From 9fdae84c7dfec6f9ef62450807b7f3d1a9bb4f50 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 20 Oct 2025 18:25:58 -0700 Subject: [PATCH 01/46] wip --- .../amazon/jdbc/PluginServiceImpl.java | 1 + .../jdbc/dialect/AuroraMysqlDialect.java | 31 +++++-------------- .../amazon/jdbc/dialect/AuroraPgDialect.java | 31 +++++-------------- ...AuroraInitialConnectionStrategyPlugin.java | 4 +++ .../ClusterAwareWriterFailoverHandler.java | 2 ++ .../failover/FailoverConnectionPlugin.java | 1 + .../failover2/FailoverConnectionPlugin.java | 1 + .../plugin/staledns/AuroraStaleDnsHelper.java | 2 ++ 8 files changed, 27 insertions(+), 46 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 4bf4f62db..d60865306 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -729,6 +729,7 @@ public void updateDialect(final @NonNull Connection connection) throws SQLExcept final HostListProviderSupplier supplier = this.dialect.getHostListProvider(); this.setHostListProvider(supplier.getProvider(this.props, this.originalUrl, this.servicesContainer)); + // TODO: refreshHostList this.refreshHostList(connection); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index e64352899..db85e28be 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -22,10 +22,7 @@ import java.sql.Statement; import java.util.Collections; import java.util.List; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; -import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; public class AuroraMysqlDialect extends MysqlDialect implements BlueGreenDialect { @@ -89,26 +86,14 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> { - final PluginService pluginService = servicesContainer.getPluginService(); - if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - IS_WRITER_QUERY); - } - return new AuroraHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY); - }; + return (properties, initialUrl, servicesContainer) -> new MonitoringRdsHostListProvider( + properties, + initialUrl, + servicesContainer, + TOPOLOGY_QUERY, + NODE_ID_QUERY, + IS_READER_QUERY, + IS_WRITER_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index c81d85f70..7af673e4e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -21,10 +21,7 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.logging.Logger; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; -import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; /** @@ -139,26 +136,14 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> { - final PluginService pluginService = servicesContainer.getPluginService(); - if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - IS_WRITER_QUERY); - } - return new AuroraHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY); - }; + return (properties, initialUrl, servicesContainer) -> new MonitoringRdsHostListProvider( + properties, + initialUrl, + servicesContainer, + TOPOLOGY_QUERY, + NODE_ID_QUERY, + IS_READER_QUERY, + IS_WRITER_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index 01b9bf8e9..ddf77a3ce 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -194,6 +194,7 @@ private Connection getVerifiedWriterConnection( // Writer is not found. It seems that topology is outdated. writerCandidateConn = connectFunc.call(); + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(writerCandidateConn); writerCandidate = this.pluginService.identifyConnection(writerCandidateConn); @@ -215,6 +216,7 @@ private Connection getVerifiedWriterConnection( if (this.pluginService.getHostRole(writerCandidateConn) != HostRole.WRITER) { // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(writerCandidateConn); this.closeConnection(writerCandidateConn); this.delay(retryDelayMs); @@ -271,6 +273,7 @@ private Connection getVerifiedReaderConnection( // Reader is not found. It seems that topology is outdated. readerCandidateConn = connectFunc.call(); + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(readerCandidateConn); readerCandidate = this.pluginService.identifyConnection(readerCandidateConn); @@ -305,6 +308,7 @@ private Connection getVerifiedReaderConnection( if (this.pluginService.getHostRole(readerCandidateConn) != HostRole.READER) { // If the new connection resolves to a writer instance, this means the topology is outdated. // Force refresh to update the topology. + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(readerCandidateConn); if (this.hasNoReaders()) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 8504842c6..9ffd4ef65 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -283,6 +283,7 @@ public WriterFailoverResult call() { } conn = this.pluginService.forceConnect(this.originalWriterHost, this.props); + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(conn); latestTopology = this.pluginService.getAllHosts(); } catch (final SQLException exception) { @@ -444,6 +445,7 @@ private boolean refreshTopologyAndConnectToNewWriter() throws InterruptedExcepti while (true) { try { + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(this.currentReaderConnection); final List topology = this.pluginService.getAllHosts(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index ef2f95550..be0b229ce 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -936,6 +936,7 @@ public Connection connect( } if (isInitialConnection) { + // TODO: refreshHostList this.pluginService.refreshHostList(conn); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 1449ca514..5ad38cda9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -787,6 +787,7 @@ public Connection connect( } if (isInitialConnection) { + // TODO: refreshHostList this.pluginService.refreshHostList(conn); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 682c3080f..0ae4efb16 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -91,8 +91,10 @@ public Connection getVerifiedConnection( // This is if-statement is only reached if the connection url is a writer cluster endpoint. // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. + // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(conn); } else { + // TODO: refreshHostList this.pluginService.refreshHostList(conn); } From 8babd96b234c9538b88d3a75b35cbf915a19f0a3 Mon Sep 17 00:00:00 2001 From: Adnan Khan Date: Tue, 21 Oct 2025 18:20:12 -0400 Subject: [PATCH 02/46] ci: scope down GitHub Token permissions (#1571) --- .github/workflows/main.yml | 3 +++ .github/workflows/maven_release.yml | 3 +++ .github/workflows/maven_snapshot.yml | 3 +++ .github/workflows/remove-old-artifacts.yml | 3 +++ .github/workflows/run-hibernate-orm-tests.yml | 3 +++ .github/workflows/run-standard-integration-tests.yml | 3 +++ 6 files changed, 18 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0325b382..5d780649a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: markdown-link-check: runs-on: ubuntu-latest diff --git a/.github/workflows/maven_release.yml b/.github/workflows/maven_release.yml index c5ad886ff..fa4d699a0 100644 --- a/.github/workflows/maven_release.yml +++ b/.github/workflows/maven_release.yml @@ -5,6 +5,9 @@ on: types: - published +permissions: + contents: read + jobs: ubuntu-latest-jdbc-wrapper-release-to-maven: name: 'Build And Release to Maven' diff --git a/.github/workflows/maven_snapshot.yml b/.github/workflows/maven_snapshot.yml index fda0edcd3..cde9b6337 100644 --- a/.github/workflows/maven_snapshot.yml +++ b/.github/workflows/maven_snapshot.yml @@ -6,6 +6,9 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: ubuntu-latest-jdbc-wrapper-snapshot-to-maven: name: 'Build And Upload Snapshot to Maven' diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml index 60e2408d3..0a1d96058 100644 --- a/.github/workflows/remove-old-artifacts.yml +++ b/.github/workflows/remove-old-artifacts.yml @@ -5,6 +5,9 @@ on: # Every day at 1am - cron: '0 1 * * *' +permissions: + actions: write + jobs: remove-old-artifacts: runs-on: ubuntu-latest diff --git a/.github/workflows/run-hibernate-orm-tests.yml b/.github/workflows/run-hibernate-orm-tests.yml index c6fc6d948..e1138a27f 100644 --- a/.github/workflows/run-hibernate-orm-tests.yml +++ b/.github/workflows/run-hibernate-orm-tests.yml @@ -6,6 +6,9 @@ on: branches: - main +permissions: + contents: read + jobs: hibernate-integration-tests: name: 'Run Hibernate ORM integration tests' diff --git a/.github/workflows/run-standard-integration-tests.yml b/.github/workflows/run-standard-integration-tests.yml index 1471e55da..43309463f 100644 --- a/.github/workflows/run-standard-integration-tests.yml +++ b/.github/workflows/run-standard-integration-tests.yml @@ -12,6 +12,9 @@ on: - '**/release_draft.yml' - '**/maven*.yml' +permissions: + contents: read + jobs: standard-integration-tests: name: 'Run standard container integration tests' From 9ce95e532e217b2bdd6fcb00ca7fc6eb37f445e0 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 21 Oct 2025 17:19:02 -0700 Subject: [PATCH 03/46] wip --- .../main/java/software/amazon/jdbc/PluginServiceImpl.java | 2 +- .../plugin/AuroraInitialConnectionStrategyPlugin.java | 8 ++++---- .../failover/ClusterAwareWriterFailoverHandler.java | 4 ++-- .../jdbc/plugin/failover/FailoverConnectionPlugin.java | 2 +- .../jdbc/plugin/failover2/FailoverConnectionPlugin.java | 2 +- .../amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index d60865306..dd8aa420b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -730,7 +730,7 @@ public void updateDialect(final @NonNull Connection connection) throws SQLExcept final HostListProviderSupplier supplier = this.dialect.getHostListProvider(); this.setHostListProvider(supplier.getProvider(this.props, this.originalUrl, this.servicesContainer)); // TODO: refreshHostList - this.refreshHostList(connection); + this.refreshHostList(); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index ddf77a3ce..568f12cb6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -195,7 +195,7 @@ private Connection getVerifiedWriterConnection( // Writer is not found. It seems that topology is outdated. writerCandidateConn = connectFunc.call(); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(writerCandidateConn); + this.pluginService.forceRefreshHostList(); writerCandidate = this.pluginService.identifyConnection(writerCandidateConn); if (writerCandidate == null || writerCandidate.getRole() != HostRole.WRITER) { @@ -217,7 +217,7 @@ private Connection getVerifiedWriterConnection( // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(writerCandidateConn); + this.pluginService.forceRefreshHostList(); this.closeConnection(writerCandidateConn); this.delay(retryDelayMs); continue; @@ -274,7 +274,7 @@ private Connection getVerifiedReaderConnection( // Reader is not found. It seems that topology is outdated. readerCandidateConn = connectFunc.call(); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(readerCandidateConn); + this.pluginService.forceRefreshHostList(); readerCandidate = this.pluginService.identifyConnection(readerCandidateConn); if (readerCandidate == null) { @@ -309,7 +309,7 @@ private Connection getVerifiedReaderConnection( // If the new connection resolves to a writer instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(readerCandidateConn); + this.pluginService.forceRefreshHostList(); if (this.hasNoReaders()) { // It seems that cluster has no readers. Simulate Aurora reader cluster endpoint logic diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 9ffd4ef65..49f92b78b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -284,7 +284,7 @@ public WriterFailoverResult call() { conn = this.pluginService.forceConnect(this.originalWriterHost, this.props); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(conn); + this.pluginService.forceRefreshHostList(); latestTopology = this.pluginService.getAllHosts(); } catch (final SQLException exception) { // Propagate exceptions that are not caused by network errors. @@ -446,7 +446,7 @@ private boolean refreshTopologyAndConnectToNewWriter() throws InterruptedExcepti while (true) { try { // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(this.currentReaderConnection); + this.pluginService.forceRefreshHostList(); final List topology = this.pluginService.getAllHosts(); if (!topology.isEmpty()) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index be0b229ce..85dd4a52a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -937,7 +937,7 @@ public Connection connect( if (isInitialConnection) { // TODO: refreshHostList - this.pluginService.refreshHostList(conn); + this.pluginService.refreshHostList(); } return conn; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 5ad38cda9..19b8710a4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -788,7 +788,7 @@ public Connection connect( if (isInitialConnection) { // TODO: refreshHostList - this.pluginService.refreshHostList(conn); + this.pluginService.refreshHostList(); } return conn; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 0ae4efb16..6ad27b8a2 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -92,10 +92,10 @@ public Connection getVerifiedConnection( // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(conn); + this.pluginService.forceRefreshHostList(); } else { // TODO: refreshHostList - this.pluginService.refreshHostList(conn); + this.pluginService.refreshHostList(); } LOGGER.finest(() -> Utils.logTopology(this.pluginService.getAllHosts())); From b993e25c84a723f1ce6f26dfcfcb64ede8ce3113 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 27 Oct 2025 13:27:17 -0700 Subject: [PATCH 04/46] dialect cleanup wip --- .../jdbc/dialect/AuroraDialectUtils.java | 110 +++++++++++ .../jdbc/dialect/AuroraMysqlDialect.java | 26 ++- .../amazon/jdbc/dialect/AuroraPgDialect.java | 28 ++- .../jdbc/dialect/MultiAzDialectUtils.java | 108 +++++++++++ .../RdsMultiAzDbClusterMysqlDialect.java | 30 ++- .../dialect/RdsMultiAzDbClusterPgDialect.java | 30 ++- .../amazon/jdbc/dialect/TopologyDialect.java | 43 +++++ .../jdbc/dialect/TopologyQueryHostSpec.java | 49 +++++ .../hostlistprovider/RdsHostListProvider.java | 11 +- .../ClusterTopologyMonitorImpl.java | 179 ++---------------- .../MonitoringRdsHostListProvider.java | 27 +-- .../MonitoringRdsMultiAzHostListProvider.java | 74 -------- .../MultiAzClusterTopologyMonitorImpl.java | 128 ------------- .../jdbc/plugin/DefaultConnectionPlugin.java | 1 + .../amazon/jdbc/util/TopologyUtils.java | 149 +++++++++++++++ .../util/monitoring/MonitorServiceImpl.java | 2 - ..._advanced_jdbc_wrapper_messages.properties | 14 +- 17 files changed, 608 insertions(+), 401 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsMultiAzHostListProvider.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MultiAzClusterTopologyMonitorImpl.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java new file mode 100644 index 000000000..0c8c0221c --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -0,0 +1,110 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.jsoup.internal.StringUtil; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.StringUtils; +import software.amazon.jdbc.util.Utils; + +public class AuroraDialectUtils { + private static final Logger LOGGER = Logger.getLogger(AuroraDialectUtils.class.getName()); + protected String writerIdQuery; + + public AuroraDialectUtils(String writerIdQuery) { + this.writerIdQuery = writerIdQuery; + } + + public @Nullable List processQueryResults(ResultSet resultSet) + throws SQLException { + if (resultSet.getMetaData().getColumnCount() == 0) { + // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. + LOGGER.finest(Messages.get("AuroraTopologyProcessor.unexpectedTopologyQueryColumnCount")); + return null; + } + + // Data is result set is ordered by last updated time so the latest records go last. + // When adding hosts to a map, the newer records replace the older ones. + List hosts = new ArrayList<>(); + while (resultSet.next()) { + try { + hosts.add(createHost(resultSet)); + } catch (Exception e) { + LOGGER.finest( + Messages.get("AuroraTopologyProcessor.errorProcessingQueryResults", new Object[]{e.getMessage()})); + return null; + } + } + + return hosts; + } + + protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { + // According to the topology query the result set should contain 4 columns: + // node ID, 1/0 (writer/reader), CPU utilization, instance lag + String hostName = resultSet.getString(1); + final boolean isWriter = resultSet.getBoolean(2); + final float cpuUtilization = resultSet.getFloat(3); + final float instanceLag = resultSet.getFloat(4); + Timestamp lastUpdateTime; + try { + lastUpdateTime = resultSet.getTimestamp(5); + } catch (Exception e) { + lastUpdateTime = Timestamp.from(Instant.now()); + } + + // Calculate weight based on node lag in time and CPU utilization. + final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); + + return new TopologyQueryHostSpec(hostName, isWriter, weight, lastUpdateTime); + } + + public boolean isWriterInstance(final Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { + if (resultSet.next()) { + return !StringUtils.isNullOrEmpty(resultSet.getString(1)); + } + } + } + + return false; + } + + // Returns a writer node ID if connected to a writer node. Returns null otherwise. + protected String getWriterNodeId(final Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { + if (resultSet.next()) { + return resultSet.getString(1); + } + } + } + return null; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index db85e28be..bead002ea 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -22,9 +22,10 @@ import java.sql.Statement; import java.util.Collections; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; -public class AuroraMysqlDialect extends MysqlDialect implements BlueGreenDialect { +public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, BlueGreenDialect { private static final String TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " @@ -47,6 +48,8 @@ public class AuroraMysqlDialect extends MysqlDialect implements BlueGreenDialect "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; + private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); + @Override public boolean isDialect(final Connection connection) { Statement stmt = null; @@ -113,5 +116,26 @@ public boolean isBlueGreenStatusAvailable(final Connection connection) { } } + @Override + public String getTopologyQuery() { + return TOPOLOGY_QUERY; + } + + @Override + public @Nullable List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + throws SQLException { + return AuroraMysqlDialect.dialectUtils.processQueryResults(rs); + } + + @Override + @Nullable public String getWriterId(final Connection connection) { + // The Aurora topology query can detect the writer without a suggested writer ID, so we intentionally return null. + return null; + } + + @Override + public boolean isWriterInstance(Connection connection) throws SQLException { + return dialectUtils.isWriterInstance(connection); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 7af673e4e..e85a36ef0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -20,7 +20,9 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.List; import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.util.DriverInfo; @@ -28,7 +30,7 @@ * Suitable for the following AWS PG configurations. * - Regional Cluster */ -public class AuroraPgDialect extends PgDialect implements AuroraLimitlessDialect, BlueGreenDialect { +public class AuroraPgDialect extends PgDialect implements TopologyDialect, AuroraLimitlessDialect, BlueGreenDialect { private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); private static final String extensionsSql = @@ -65,6 +67,8 @@ public class AuroraPgDialect extends PgDialect implements AuroraLimitlessDialect private static final String TOPOLOGY_TABLE_EXIST_QUERY = "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc"; + private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); + @Override public boolean isDialect(final Connection connection) { if (!super.isDialect(connection)) { @@ -146,6 +150,28 @@ public HostListProviderSupplier getHostListProvider() { IS_WRITER_QUERY); } + @Override + public String getTopologyQuery() { + return TOPOLOGY_QUERY; + } + + @Override + @Nullable public String getWriterId(final Connection connection) { + // The Aurora topology query can detect the writer without a suggested writer ID, so we intentionally return null. + return null; + } + + @Override + public boolean isWriterInstance(Connection connection) throws SQLException { + return AuroraPgDialect.dialectUtils.isWriterInstance(connection); + } + + @Override + public @Nullable List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + throws SQLException { + return AuroraPgDialect.dialectUtils.processQueryResults(rs); + } + @Override public String getLimitlessRouterEndpointQuery() { return LIMITLESS_ROUTER_ENDPOINT_QUERY; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java new file mode 100644 index 000000000..3d885033f --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -0,0 +1,108 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.StringUtils; + +public class MultiAzDialectUtils { + private static final Logger LOGGER = Logger.getLogger(MultiAzDialectUtils.class.getName()); + private final String writerIdQuery; + private final String writerIdQueryColumn; + private final String instanceIdQuery; + + public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, String instanceIdQuery) { + this.writerIdQuery = writerIdQuery; + this.writerIdQueryColumn = writerIdQueryColumn; + this.instanceIdQuery = instanceIdQuery; + } + + public @Nullable List processQueryResults(ResultSet resultSet, String suggestedWriterId) + throws SQLException { + List hosts = new ArrayList<>(); + while (resultSet.next()) { + try { + final TopologyQueryHostSpec host = createHost(resultSet, suggestedWriterId); + hosts.add(host); + } catch (Exception e) { + LOGGER.finest( + Messages.get("ClusterTopologyMonitorImpl.errorProcessingQueryResults", new Object[]{e.getMessage()})); + return null; + } + } + + return hosts; + } + + protected TopologyQueryHostSpec createHost( + final ResultSet resultSet, + final String suggestedWriterNodeId) throws SQLException { + + String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" + String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" + String hostId = resultSet.getString("id"); // "1034958454" + final boolean isWriter = hostId.equals(suggestedWriterNodeId); + + return new TopologyQueryHostSpec(instanceName, isWriter, 0, Timestamp.from(Instant.now())); + } + + public @Nullable String getSuggestedWriterId(Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { + if (resultSet.next()) { + String writerId = resultSet.getString(this.writerIdQueryColumn); + if (!StringUtils.isNullOrEmpty(writerId)) { + // Replica status exists and shows a writer instance ID, which means that this instance is a reader. + return writerId; + } + } + } + + // Replica status doesn't exist, which means that this instance is a writer. + try (final ResultSet resultSet = stmt.executeQuery(this.instanceIdQuery)) { + if (resultSet.next()) { + return resultSet.getString(1); + } + } + } + return null; + } + + public boolean isWriterInstance(final Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { + if (resultSet.next()) { + String nodeId = resultSet.getString(this.writerIdQueryColumn); + // The writer ID is only returned when connected to a reader, so if the query does not return a value, it + // means we are connected to a writer. + return StringUtils.isNullOrEmpty(nodeId); + } + } + } + return false; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index 930cf1631..439135aa8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -24,17 +24,18 @@ import java.util.List; import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.RdsMultiAzDbClusterListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsMultiAzHostListProvider; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverRestriction; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect { +public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect implements TopologyDialect { private static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; @@ -54,6 +55,8 @@ public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect { EnumSet.of(FailoverRestriction.DISABLE_TASK_A, FailoverRestriction.ENABLE_WRITER_IN_TASK_B); protected final RdsUtils rdsUtils = new RdsUtils(); + protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( + FETCH_WRITER_NODE_QUERY, FETCH_WRITER_NODE_QUERY_COLUMN_NAME, NODE_ID_QUERY); @Override public boolean isDialect(final Connection connection) { @@ -97,7 +100,7 @@ public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsMultiAzHostListProvider( + return new MonitoringRdsHostListProvider( properties, initialUrl, servicesContainer, @@ -136,4 +139,25 @@ public void prepareConnectProperties( public EnumSet getFailoverRestrictions() { return RDS_MULTI_AZ_RESTRICTIONS; } + + @Override + public String getTopologyQuery() { + return TOPOLOGY_QUERY; + } + + @Override + public List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + throws SQLException { + return dialectUtils.processQueryResults(rs, suggestedWriterId); + } + + @Override + public @Nullable String getWriterId(Connection connection) throws SQLException { + return dialectUtils.getSuggestedWriterId(connection); + } + + @Override + public boolean isWriterInstance(Connection connection) throws SQLException { + return dialectUtils.isWriterInstance(connection); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index eb3796adb..fd0664c86 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -20,17 +20,18 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.util.Collections; import java.util.List; import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; import software.amazon.jdbc.hostlistprovider.RdsMultiAzDbClusterListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsMultiAzHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; -public class RdsMultiAzDbClusterPgDialect extends PgDialect { +public class RdsMultiAzDbClusterPgDialect extends PgDialect implements TopologyDialect { private static final Logger LOGGER = Logger.getLogger(RdsMultiAzDbClusterPgDialect.class.getName()); @@ -54,6 +55,9 @@ public class RdsMultiAzDbClusterPgDialect extends PgDialect { private static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; + protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( + FETCH_WRITER_NODE_QUERY, FETCH_WRITER_NODE_QUERY_COLUMN_NAME, NODE_ID_QUERY); + @Override public ExceptionHandler getExceptionHandler() { if (exceptionHandler == null) { @@ -83,7 +87,7 @@ public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsMultiAzHostListProvider( + return new MonitoringRdsHostListProvider( properties, initialUrl, servicesContainer, @@ -107,4 +111,24 @@ public HostListProviderSupplier getHostListProvider() { } }; } + + @Override + public String getTopologyQuery() { + return TOPOLOGY_QUERY; + } + + @Override + public @Nullable String getWriterId(final Connection connection) throws SQLException { + return dialectUtils.getSuggestedWriterId(connection); + } + + @Override + public boolean isWriterInstance(Connection connection) throws SQLException { + return dialectUtils.isWriterInstance(connection); + } + + @Override + public @Nullable List processQueryResults(ResultSet rs, String suggestedWriterId) throws SQLException { + return this.dialectUtils.processQueryResults(rs, suggestedWriterId); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java new file mode 100644 index 000000000..dc0cdf108 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -0,0 +1,43 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; + +public interface TopologyDialect { + String getTopologyQuery(); + + @Nullable + List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + throws SQLException; + + @Nullable + String getWriterId(final Connection connection) throws SQLException; + + // TODO: can we remove this and use getHostRole instead? + boolean isWriterInstance(final Connection connection) throws SQLException; + + HostSpec identifyConnection(Connection connection) throws SQLException; + + HostRole getHostRole(Connection conn) throws SQLException; +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java new file mode 100644 index 000000000..4ee19c2c4 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java @@ -0,0 +1,49 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Timestamp; + +public class TopologyQueryHostSpec { + private final String instanceId; + private final boolean isWriter; + private final long weight; + private final Timestamp lastUpdateTime; + + public TopologyQueryHostSpec(String instanceId, boolean isWriter, long weight, Timestamp lastUpdateTime) { + this.instanceId = instanceId; + this.isWriter = isWriter; + this.weight = weight; + this.lastUpdateTime = lastUpdateTime; + } + + public String getInstanceId() { + return instanceId; + } + + public boolean isWriter() { + return isWriter; + } + + public long getWeight() { + return weight; + } + + public Timestamp getLastUpdateTime() { + return lastUpdateTime; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 738eebcc3..89824556d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -95,9 +95,6 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected final FullServicesContainer servicesContainer; protected final HostListProviderService hostListProviderService; protected final String originalUrl; - protected final String topologyQuery; - protected final String nodeIdQuery; - protected final String isReaderQuery; protected RdsUrlType rdsUrlType; protected long refreshRateNano = CLUSTER_TOPOLOGY_REFRESH_RATE_MS.defaultValue != null ? TimeUnit.MILLISECONDS.toNanos(Long.parseLong(CLUSTER_TOPOLOGY_REFRESH_RATE_MS.defaultValue)) @@ -125,17 +122,11 @@ public class RdsHostListProvider implements DynamicHostListProvider { public RdsHostListProvider( final Properties properties, final String originalUrl, - final FullServicesContainer servicesContainer, - final String topologyQuery, - final String nodeIdQuery, - final String isReaderQuery) { + final FullServicesContainer servicesContainer) { this.properties = properties; this.originalUrl = originalUrl; this.servicesContainer = servicesContainer; this.hostListProviderService = servicesContainer.getHostListProviderService(); - this.topologyQuery = topologyQuery; - this.nodeIdQuery = nodeIdQuery; - this.isReaderQuery = isReaderQuery; } protected void init() throws SQLException { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 92227de29..fa3f42859 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -22,10 +22,7 @@ import java.sql.SQLSyntaxErrorException; import java.sql.Statement; import java.sql.Timestamp; -import java.time.Instant; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -39,12 +36,12 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PropertyDefinition; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.util.ExecutorFactory; @@ -55,6 +52,7 @@ import software.amazon.jdbc.util.ServiceUtility; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.SynchronousExecutor; +import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.monitoring.AbstractMonitor; @@ -79,13 +77,11 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected final long refreshRateNano; protected final long highRefreshRateNano; + protected final TopologyDialect dialect; protected final FullServicesContainer servicesContainer; protected final Properties properties; protected final Properties monitoringProperties; protected final HostSpec initialHostSpec; - protected final String topologyQuery; - protected final String nodeIdQuery; - protected final String writerTopologyQuery; protected final HostSpec clusterInstanceTemplate; protected String clusterId; @@ -107,27 +103,23 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust public ClusterTopologyMonitorImpl( final FullServicesContainer servicesContainer, + final TopologyDialect dialect, final String clusterId, final HostSpec initialHostSpec, final Properties properties, final HostSpec clusterInstanceTemplate, final long refreshRateNano, - final long highRefreshRateNano, - final String topologyQuery, - final String writerTopologyQuery, - final String nodeIdQuery) { + final long highRefreshRateNano) { super(monitorTerminationTimeoutSec); - this.clusterId = clusterId; this.servicesContainer = servicesContainer; + this.dialect = dialect; + this.clusterId = clusterId; this.initialHostSpec = initialHostSpec; this.clusterInstanceTemplate = clusterInstanceTemplate; this.properties = properties; this.refreshRateNano = refreshRateNano; this.highRefreshRateNano = highRefreshRateNano; - this.topologyQuery = topologyQuery; - this.writerTopologyQuery = writerTopologyQuery; - this.nodeIdQuery = nodeIdQuery; this.monitoringProperties = PropertyUtils.copyProperties(properties); this.properties.stringPropertyNames().stream() @@ -526,7 +518,7 @@ protected List openAnyConnectionAndUpdateTopology() { new Object[]{this.initialHostSpec.getHost()})); try { - if (!StringUtils.isNullOrEmpty(this.getWriterNodeId(this.monitoringConnection.get()))) { + if (this.dialect.isWriterInstance(this.monitoringConnection.get())) { this.isVerifiedWriterConnection = true; writerVerifiedByThisThread = true; @@ -537,7 +529,7 @@ protected List openAnyConnectionAndUpdateTopology() { "ClusterTopologyMonitorImpl.writerMonitoringConnection", new Object[]{this.writerHostSpec.get().getHost()})); } else { - final String nodeId = this.getNodeId(this.monitoringConnection.get()); + final String nodeId = this.dialect.getInstanceId(this.monitoringConnection.get()); if (!StringUtils.isNullOrEmpty(nodeId)) { this.writerHostSpec.set(this.createHost(nodeId, true, 0, null)); LOGGER.finest( @@ -580,20 +572,6 @@ protected List openAnyConnectionAndUpdateTopology() { return hosts; } - protected String getNodeId(final Connection connection) { - try { - try (final Statement stmt = connection.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.nodeIdQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } catch (SQLException ex) { - // do nothing - } - return null; - } - protected void closeConnection(final @Nullable Connection connection) { try { if (connection != null && !connection.isClosed()) { @@ -633,7 +611,7 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { return null; } try { - final List hosts = this.queryForTopology(connection); + final List hosts = this.topologyUtils.queryForTopology(connection); if (!Utils.isNullOrEmpty(hosts)) { this.updateTopologyCache(hosts); } @@ -657,128 +635,6 @@ protected void updateTopologyCache(final @NonNull List hosts) { } } - // Returns a writer node ID if connected to a writer node. Returns null otherwise. - protected String getWriterNodeId(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.writerTopologyQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } - return null; - } - - protected @Nullable List queryForTopology(final Connection conn) throws SQLException { - int networkTimeout = -1; - try { - networkTimeout = conn.getNetworkTimeout(); - // The topology query is not monitored by the EFM plugin, so it needs a socket timeout - if (networkTimeout == 0) { - conn.setNetworkTimeout(networkTimeoutExecutor, defaultTopologyQueryTimeoutMs); - } - } catch (SQLException e) { - LOGGER.warning(() -> Messages.get("ClusterTopologyMonitorImpl.errorGettingNetworkTimeout", - new Object[] {e.getMessage()})); - } - - final String suggestedWriterNodeId = this.getSuggestedWriterNodeId(conn); - try (final Statement stmt = conn.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.topologyQuery)) { - return this.processQueryResults(resultSet, suggestedWriterNodeId); - } catch (final SQLSyntaxErrorException e) { - throw new SQLException(Messages.get("ClusterTopologyMonitorImpl.invalidQuery"), e); - } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); - } - } - } - - protected String getSuggestedWriterNodeId(final Connection connection) throws SQLException { - // Aurora topology query can detect a writer for itself so it doesn't need any suggested writer node ID. - return null; // intentionally null - } - - protected @Nullable List processQueryResults( - final ResultSet resultSet, - final String suggestedWriterNodeId) throws SQLException { - - final HashMap hostMap = new HashMap<>(); - - if (resultSet.getMetaData().getColumnCount() == 0) { - // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.unexpectedTopologyQueryColumnCount")); - return null; - } - - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - while (resultSet.next()) { - try { - final HostSpec host = createHost(resultSet, suggestedWriterNodeId); - hostMap.put(host.getHost(), host); - } catch (Exception e) { - LOGGER.finest( - Messages.get("ClusterTopologyMonitorImpl.errorProcessingQueryResults", new Object[]{e.getMessage()})); - return null; - } - } - - final List hosts = new ArrayList<>(); - final List writers = new ArrayList<>(); - - for (final HostSpec host : hostMap.values()) { - if (host.getRole() != HostRole.WRITER) { - hosts.add(host); - } else { - writers.add(host); - } - } - - int writerCount = writers.size(); - - if (writerCount == 0) { - LOGGER.warning(() -> Messages.get("ClusterTopologyMonitorImpl.invalidTopology")); - hosts.clear(); - } else if (writerCount == 1) { - hosts.add(writers.get(0)); - } else { - // Take the latest updated writer node as the current writer. All others will be ignored. - List sortedWriters = writers.stream() - .sorted(Comparator.comparing(HostSpec::getLastUpdateTime, Comparator.nullsLast(Comparator.reverseOrder()))) - .collect(Collectors.toList()); - hosts.add(sortedWriters.get(0)); - } - - return hosts; - } - - protected HostSpec createHost( - final ResultSet resultSet, - final String suggestedWriterNodeId) throws SQLException { - - // suggestedWriterNodeId is not used for Aurora clusters. Topology query can detect a writer for itself. - - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), CPU utilization, node lag in time. - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final float cpuUtilization = resultSet.getFloat(3); - final float nodeLag = resultSet.getFloat(4); - Timestamp lastUpdateTime; - try { - lastUpdateTime = resultSet.getTimestamp(5); - } catch (Exception e) { - lastUpdateTime = Timestamp.from(Instant.now()); - } - - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); - - return createHost(hostName, isWriter, weight, lastUpdateTime); - } - protected HostSpec createHost( String nodeName, final boolean isWriter, @@ -854,10 +710,9 @@ public void run() { if (connection != null) { - String writerId = null; + boolean isWriter = false; try { - writerId = this.monitor.getWriterNodeId(connection); - + isWriter = this.dialect.isWriterInstance(connection); } catch (SQLSyntaxErrorException ex) { LOGGER.severe(() -> Messages.get("NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); @@ -868,11 +723,11 @@ public void run() { connection = null; } - if (!StringUtils.isNullOrEmpty(writerId)) { + if (isWriter) { try { if (this.servicesContainer.getPluginService().getHostRole(connection) != HostRole.WRITER) { // The first connection after failover may be stale. - writerId = null; + isWriter = false; } } catch (SQLException e) { // Invalid connection, retry. @@ -880,7 +735,7 @@ public void run() { } } - if (!StringUtils.isNullOrEmpty(writerId)) { + if (isWriter) { // this prevents closing connection in finally block if (!this.monitor.nodeThreadsWriterConnection.compareAndSet(null, connection)) { // writer connection is already setup @@ -888,7 +743,7 @@ public void run() { } else { // writer connection is successfully set to writerConnection - LOGGER.fine(Messages.get("NodeMonitoringThread.detectedWriter", new Object[]{writerId})); + LOGGER.fine(Messages.get("NodeMonitoringThread.detectedWriter", new Object[]{hostSpec.getUrl()})); // When nodeThreadsWriterConnection and nodeThreadsWriterHostSpec are both set, the topology monitor may // set ignoreNewTopologyRequestsEndTimeNano, in which case other threads will use the cached topology // for the ignore duration, so we need to update the topology before setting nodeThreadsWriterHostSpec. @@ -938,7 +793,7 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla List hosts; try { - hosts = this.monitor.queryForTopology(connection); + hosts = this.monitor.topologyUtils.queryForTopology(connection); if (hosts == null) { return; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index 0bc4cf897..f6dba75d0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -29,9 +29,13 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.cleanup.CanReleaseResources; +import software.amazon.jdbc.dialect.Dialect; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.monitoring.MonitorService; import software.amazon.jdbc.util.storage.StorageService; @@ -53,20 +57,14 @@ public class MonitoringRdsHostListProvider extends RdsHostListProvider protected final FullServicesContainer servicesContainer; protected final PluginService pluginService; protected final long highRefreshRateNano; - protected final String writerTopologyQuery; public MonitoringRdsHostListProvider( final Properties properties, final String originalUrl, - final FullServicesContainer servicesContainer, - final String topologyQuery, - final String nodeIdQuery, - final String isReaderQuery, - final String writerTopologyQuery) { - super(properties, originalUrl, servicesContainer, topologyQuery, nodeIdQuery, isReaderQuery); + final FullServicesContainer servicesContainer) { + super(properties, originalUrl, servicesContainer); this.servicesContainer = servicesContainer; this.pluginService = servicesContainer.getPluginService(); - this.writerTopologyQuery = writerTopologyQuery; this.highRefreshRateNano = TimeUnit.MILLISECONDS.toNanos( CLUSTER_TOPOLOGY_HIGH_REFRESH_RATE_MS.getLong(this.properties)); } @@ -81,6 +79,13 @@ protected void init() throws SQLException { } protected ClusterTopologyMonitor initMonitor() throws SQLException { + Dialect dialect = this.servicesContainer.getPluginService().getDialect(); + if (!(dialect instanceof TopologyDialect)) { + throw new SQLException( + Messages.get("TopologyUtils.topologyDialectRequired", new Object[]{dialect.getClass().getName()})); + } + + TopologyDialect topologyDialect = (TopologyDialect) dialect; return this.servicesContainer.getMonitorService().runIfAbsent( ClusterTopologyMonitorImpl.class, this.clusterId, @@ -88,15 +93,13 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.properties, (servicesContainer) -> new ClusterTopologyMonitorImpl( this.servicesContainer, + topologyDialect, this.clusterId, this.initialHostSpec, this.properties, this.clusterInstanceTemplate, this.refreshRateNano, - this.highRefreshRateNano, - this.topologyQuery, - this.writerTopologyQuery, - this.nodeIdQuery)); + this.highRefreshRateNano)); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsMultiAzHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsMultiAzHostListProvider.java deleted file mode 100644 index c11da2be9..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsMultiAzHostListProvider.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider.monitoring; - -import java.sql.SQLException; -import java.util.Properties; -import java.util.logging.Logger; -import software.amazon.jdbc.util.FullServicesContainer; - -public class MonitoringRdsMultiAzHostListProvider extends MonitoringRdsHostListProvider { - - private static final Logger LOGGER = Logger.getLogger(MonitoringRdsMultiAzHostListProvider.class.getName()); - - protected final String fetchWriterNodeQuery; - protected final String fetchWriterNodeColumnName; - - public MonitoringRdsMultiAzHostListProvider( - final Properties properties, - final String originalUrl, - final FullServicesContainer servicesContainer, - final String topologyQuery, - final String nodeIdQuery, - final String isReaderQuery, - final String fetchWriterNodeQuery, - final String fetchWriterNodeColumnName) { - super( - properties, - originalUrl, - servicesContainer, - topologyQuery, - nodeIdQuery, - isReaderQuery, - ""); - this.fetchWriterNodeQuery = fetchWriterNodeQuery; - this.fetchWriterNodeColumnName = fetchWriterNodeColumnName; - } - - @Override - protected ClusterTopologyMonitor initMonitor() throws SQLException { - return this.servicesContainer.getMonitorService().runIfAbsent(MultiAzClusterTopologyMonitorImpl.class, - this.clusterId, - this.servicesContainer, - this.properties, - (servicesContainer) -> new MultiAzClusterTopologyMonitorImpl( - servicesContainer, - this.clusterId, - this.initialHostSpec, - this.properties, - this.hostListProviderService, - this.clusterInstanceTemplate, - this.refreshRateNano, - this.highRefreshRateNano, - this.topologyQuery, - this.writerTopologyQuery, - this.nodeIdQuery, - this.fetchWriterNodeQuery, - this.fetchWriterNodeColumnName)); - } - -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MultiAzClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MultiAzClusterTopologyMonitorImpl.java deleted file mode 100644 index 36bab8f90..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MultiAzClusterTopologyMonitorImpl.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider.monitoring; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.Properties; -import java.util.logging.Logger; -import software.amazon.jdbc.HostListProviderService; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.StringUtils; - -public class MultiAzClusterTopologyMonitorImpl extends ClusterTopologyMonitorImpl { - - private static final Logger LOGGER = Logger.getLogger(MultiAzClusterTopologyMonitorImpl.class.getName()); - - protected final String fetchWriterNodeQuery; - protected final String fetchWriterNodeColumnName; - - public MultiAzClusterTopologyMonitorImpl( - final FullServicesContainer servicesContainer, - final String clusterId, - final HostSpec initialHostSpec, - final Properties properties, - final HostListProviderService hostListProviderService, - final HostSpec clusterInstanceTemplate, - final long refreshRateNano, - final long highRefreshRateNano, - final String topologyQuery, - final String writerTopologyQuery, - final String nodeIdQuery, - final String fetchWriterNodeQuery, - final String fetchWriterNodeColumnName) { - super( - servicesContainer, - clusterId, - initialHostSpec, - properties, - clusterInstanceTemplate, - refreshRateNano, - highRefreshRateNano, - topologyQuery, - writerTopologyQuery, - nodeIdQuery); - this.fetchWriterNodeQuery = fetchWriterNodeQuery; - this.fetchWriterNodeColumnName = fetchWriterNodeColumnName; - } - - // Returns a writer node ID if connected to a writer node. Returns null otherwise. - @Override - protected String getWriterNodeId(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.fetchWriterNodeQuery)) { - if (resultSet.next()) { - String nodeId = resultSet.getString(this.fetchWriterNodeColumnName); - if (!StringUtils.isNullOrEmpty(nodeId)) { - // Replica status exists and shows a writer node ID. - // That means that this node (this connection) is a reader - return null; - } - } - } - // Replica status doesn't exist. That means that this node is a writer. - try (final ResultSet resultSet = stmt.executeQuery(this.nodeIdQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } - return null; - } - - @Override - protected String getSuggestedWriterNodeId(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.fetchWriterNodeQuery)) { - if (resultSet.next()) { - String nodeId = resultSet.getString(this.fetchWriterNodeColumnName); - if (!StringUtils.isNullOrEmpty(nodeId)) { - // Replica status exists and shows a writer node ID. - // That means that this node (this connection) is a reader. - // But we now what replication source is and that is a writer node. - return nodeId; - } - } - } - // Replica status doesn't exist. That means that this node is a writer. - try (final ResultSet resultSet = stmt.executeQuery(this.nodeIdQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } - return null; - } - - @Override - protected HostSpec createHost( - final ResultSet resultSet, - final String suggestedWriterNodeId) throws SQLException { - - String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" - String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" - String hostId = resultSet.getString("id"); // "1034958454" - final boolean isWriter = hostId.equals(suggestedWriterNodeId); - - return createHost(instanceName, isWriter, 0, Timestamp.from(Instant.now())); - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java index 66277275b..b879ba60b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java @@ -16,6 +16,7 @@ package software.amazon.jdbc.plugin; +import java.beans.beancontext.BeanContext; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java new file mode 100644 index 000000000..d8447ac07 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -0,0 +1,149 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.util; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLSyntaxErrorException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.dialect.TopologyQueryHostSpec; +import software.amazon.jdbc.hostavailability.HostAvailability; + +public class TopologyUtils { + private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); + protected static final int DEFAULT_QUERY_TIMEOUT_MS = 1000; + + protected final Executor networkTimeoutExecutor = new SynchronousExecutor(); + protected final TopologyDialect dialect; + protected final HostSpec clusterInstanceTemplate; + protected final HostSpec initialHostSpec; + protected final HostSpecBuilder hostSpecBuilder; + + public TopologyUtils( + TopologyDialect dialect, HostSpec clusterInstanceTemplate, HostSpec initialHostSpec, HostSpecBuilder hostSpecBuilder) { + this.dialect = dialect; + this.clusterInstanceTemplate = clusterInstanceTemplate; + this.initialHostSpec = initialHostSpec; + this.hostSpecBuilder = hostSpecBuilder; + } + + public List queryForTopology(Connection conn) throws SQLException { + int networkTimeout = -1; + try { + networkTimeout = conn.getNetworkTimeout(); + // The topology query is not monitored by the EFM plugin, so it needs a socket timeout + if (networkTimeout == 0) { + conn.setNetworkTimeout(this.networkTimeoutExecutor, DEFAULT_QUERY_TIMEOUT_MS); + } + } catch (SQLException e) { + LOGGER.warning(() -> Messages.get("TopologyUtils.errorGettingNetworkTimeout", + new Object[] {e.getMessage()})); + } + + final String writerId = this.dialect.getWriterId(conn); + try (final Statement stmt = conn.createStatement(); + final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { + List queryHosts = this.dialect.processQueryResults(resultSet, writerId); + return this.processQueryResults(queryHosts); + } catch (final SQLSyntaxErrorException e) { + throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); + } finally { + if (networkTimeout == 0 && !conn.isClosed()) { + conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); + } + } + } + + protected @Nullable List processQueryResults(@Nullable List queryHosts) { + if (queryHosts == null) { + return null; + } + + List hosts = new ArrayList<>(); + List writers = new ArrayList<>(); + for (TopologyQueryHostSpec queryHost : queryHosts) { + if (queryHost.isWriter()) { + writers.add(this.toHostspec(queryHost)); + } else { + hosts.add(this.toHostspec(queryHost)); + } + } + + int writerCount = writers.size(); + if (writerCount == 0) { + LOGGER.warning(() -> Messages.get("ClusterTopologyMonitorImpl.invalidTopology")); + return null; + } else if (writerCount == 1) { + hosts.add(writers.get(0)); + } else { + // Assume the latest updated writer instance is the current writer. Other potential writers will be ignored. + List sortedWriters = writers.stream() + .sorted(Comparator.comparing(HostSpec::getLastUpdateTime, Comparator.nullsLast(Comparator.reverseOrder()))) + .collect(Collectors.toList()); + hosts.add(sortedWriters.get(0)); + } + + return hosts; + } + + protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { + final String instanceId = queryHost.getInstanceId() == null ? "?" : queryHost.getInstanceId(); + final String endpoint = this.clusterInstanceTemplate.getHost().replace("?", instanceId); + final int port = this.clusterInstanceTemplate.isPortSpecified() + ? this.clusterInstanceTemplate.getPort() + : this.initialHostSpec.getPort(); + + final HostSpec hostSpec = this.hostSpecBuilder + .host(endpoint) + .port(port) + .role(queryHost.isWriter() ? HostRole.WRITER : HostRole.READER) + .availability(HostAvailability.AVAILABLE) + .weight(queryHost.getWeight()) + .lastUpdateTime(queryHost.getLastUpdateTime()) + .build(); + hostSpec.addAlias(instanceId); + hostSpec.setHostId(instanceId); + return hostSpec; + } + + public HostRole getHostRole(Connection conn) throws SQLException { + try (final Statement stmt = conn.createStatement(); + final ResultSet rs = stmt.executeQuery(this.isReaderQuery)) { + if (rs.next()) { + boolean isReader = rs.getBoolean(1); + return isReader ? HostRole.READER : HostRole.WRITER; + } + } catch (SQLException e) { + throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole"), e); + } + + throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole")); + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/monitoring/MonitorServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/util/monitoring/MonitorServiceImpl.java index ec33655a8..0122e5e99 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/monitoring/MonitorServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/monitoring/MonitorServiceImpl.java @@ -36,7 +36,6 @@ import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.hostlistprovider.monitoring.ClusterTopologyMonitorImpl; -import software.amazon.jdbc.hostlistprovider.monitoring.MultiAzClusterTopologyMonitorImpl; import software.amazon.jdbc.plugin.strategy.fastestresponse.NodeResponseTimeMonitor; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.ExecutorFactory; @@ -65,7 +64,6 @@ public class MonitorServiceImpl implements MonitorService, EventSubscriber { TimeUnit.MINUTES.toNanos(15), TimeUnit.MINUTES.toNanos(3), recreateOnError); suppliers.put(ClusterTopologyMonitorImpl.class, () -> new CacheContainer(defaultSettings, Topology.class)); - suppliers.put(MultiAzClusterTopologyMonitorImpl.class, () -> new CacheContainer(defaultSettings, Topology.class)); suppliers.put(NodeResponseTimeMonitor.class, () -> new CacheContainer(defaultSettings, null)); defaultSuppliers = Collections.unmodifiableMap(suppliers); } diff --git a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties index c5ea7c275..e67efb909 100644 --- a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties +++ b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties @@ -27,6 +27,10 @@ AdfsCredentialsProviderFactory.signOnPagePostActionRequestFailed=ADFS SignOn Pag AdfsCredentialsProviderFactory.signOnPageRequestFailed=ADFS SignOn Page Request Failed with HTTP status ''{0}'', reason phrase ''{1}'', and response ''{2}'' AdfsCredentialsProviderFactory.signOnPageUrl=ADFS SignOn URL: ''{0}'' +AuroraTopologyProcessor.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} +AuroraDialect.invalidTopology=The topology query returned an invalid topology - no writer instance detected. +AuroraTopologyProcessor.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. + AuthenticationToken.useCachedToken=Use cached authentication token = ''{0}'' AuthenticationToken.generatedNewToken=Generated new authentication token = ''{0}'' AuthenticationToken.javaSdkNotInClasspath=Required dependency 'AWS Java SDK RDS v2.x' is not on the classpath. @@ -355,6 +359,11 @@ TargetDriverDialectManager.useDialect=Target driver dialect set to: ''{0}'', {1} TargetDriverDialectManager.unexpectedClass=Unexpected DataSource class. Expected class(es): {0}, actual class: {1}. TargetDriverDialect.unsupported=This target driver dialect does not support this operation. MysqlConnectorJDriverHelper.canNotRegister=Can''t register driver com.mysql.cj.jdbc.Driver. + +TopologyUtils.errorGettingNetworkTimeout=An error occurred while getting the connection network timeout: {0} +TopologyUtils.topologyDialectRequired=Unable to fetch topology because the dialect does not support topology queries. Dialect: {0} +TopologyUtils.invalidQuery=An error occurred while attempting to obtain the topology because the topology query was invalid. Please ensure you are connecting to an Aurora or RDS Db cluster. + MariadbDriverHelper.canNotRegister=Can''t register driver org.mariadb.jdbc.Driver. AuroraInitialConnectionStrategyPlugin.unsupportedStrategy=Unsupported host selection strategy ''{0}''. @@ -370,9 +379,6 @@ NodeResponseTimeMonitor.openedConnection=Opened Response time connection: {0}. ClusterTopologyMonitorImpl.startMonitoringThread=[clusterId: ''{0}''] Start cluster topology monitoring thread for ''{1}''. ClusterTopologyMonitorImpl.stopMonitoringThread=Stop cluster topology monitoring thread for ''{0}''. ClusterTopologyMonitorImpl.exceptionDuringMonitoringStop=Stopping cluster topology monitoring after unhandled exception was thrown in monitoring thread for node ''{0}''. -ClusterTopologyMonitorImpl.invalidQuery=An error occurred while attempting to obtain the topology because the topology query was invalid. Please ensure you are connecting to an Aurora or RDS Db cluster. -ClusterTopologyMonitorImpl.errorGettingNetworkTimeout=An error occurred while getting the connection network timeout: {0} -ClusterTopologyMonitorImpl.invalidTopology=The topology query returned an invalid topology - no writer instance detected. ClusterTopologyMonitorImpl.topologyNotUpdated=Topology hasn''t been updated after {0} ms. ClusterTopologyMonitorImpl.openedMonitoringConnection=Opened monitoring connection to node ''{0}''. ClusterTopologyMonitorImpl.ignoringTopologyRequest=A topology refresh was requested, but the topology was already updated recently. Returning cached hosts: @@ -382,8 +388,6 @@ ClusterTopologyMonitorImpl.startingNodeMonitoringThreads=Starting node monitorin ClusterTopologyMonitorImpl.writerPickedUpFromNodeMonitors=The writer host detected by the node monitors was picked up by the topology monitor: ''{0}''. ClusterTopologyMonitorImpl.writerMonitoringConnection=The monitoring connection is connected to a writer: ''{0}''. ClusterTopologyMonitorImpl.errorFetchingTopology=An error occurred while querying for topology: {0} -ClusterTopologyMonitorImpl.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} -ClusterTopologyMonitorImpl.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. # Blue/Green Deployment bgd.inProgressConnectionClosed=Connection has been closed since Blue/Green switchover is in progress. From 9df6e1d11161608e5d311cf7072067b4cde986e4 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 28 Oct 2025 16:24:24 -0700 Subject: [PATCH 05/46] wip --- .../amazon/jdbc/dialect/AuroraMysqlDialect.java | 17 +++++++++++++++++ .../amazon/jdbc/dialect/AuroraPgDialect.java | 1 + .../amazon/jdbc/dialect/TopologyDialect.java | 6 ++++-- .../monitoring/ClusterTopologyMonitorImpl.java | 8 +++----- .../MonitoringRdsHostListProvider.java | 2 ++ .../amazon/jdbc/util/TopologyUtils.java | 4 ++++ 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index bead002ea..7c58d8b36 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -23,6 +23,8 @@ import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, BlueGreenDialect { @@ -137,5 +139,20 @@ public String getTopologyQuery() { public boolean isWriterInstance(Connection connection) throws SQLException { return dialectUtils.isWriterInstance(connection); } + + @Override + public HostSpec identifyConnection(Connection connection) throws SQLException { + return null; + } + + @Override + public HostRole getHostRole(Connection conn) throws SQLException { + return null; + } + + @Override + public String getIsReaderQuery() { + return ""; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index e85a36ef0..3f8a573d2 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -92,6 +92,7 @@ public boolean isDialect(final Connection connection) { } catch (SQLException ex) { // ignore } finally { + // TODO: switch to try-with-resources here and check for any other places that can be cleaned up similarly if (stmt != null) { try { stmt.close(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index dc0cdf108..98d421c17 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -37,7 +37,9 @@ List processQueryResults(ResultSet rs, @Nullable String s // TODO: can we remove this and use getHostRole instead? boolean isWriterInstance(final Connection connection) throws SQLException; - HostSpec identifyConnection(Connection connection) throws SQLException; + HostSpec identifyConnection(Connection connection) throws SQLException - HostRole getHostRole(Connection conn) throws SQLException; + String getIsReaderQuery(); + + String get } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index fa3f42859..0f763659d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -17,10 +17,8 @@ package software.amazon.jdbc.hostlistprovider.monitoring; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; -import java.sql.Statement; import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; @@ -77,7 +75,7 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected final long refreshRateNano; protected final long highRefreshRateNano; - protected final TopologyDialect dialect; + protected final TopologyUtils topologyUtils; protected final FullServicesContainer servicesContainer; protected final Properties properties; protected final Properties monitoringProperties; @@ -103,7 +101,7 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust public ClusterTopologyMonitorImpl( final FullServicesContainer servicesContainer, - final TopologyDialect dialect, + final TopologyUtils topologyUtils, final String clusterId, final HostSpec initialHostSpec, final Properties properties, @@ -113,7 +111,7 @@ public ClusterTopologyMonitorImpl( super(monitorTerminationTimeoutSec); this.servicesContainer = servicesContainer; - this.dialect = dialect; + this.topologyUtils = topologyUtils; this.clusterId = clusterId; this.initialHostSpec = initialHostSpec; this.clusterInstanceTemplate = clusterInstanceTemplate; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index f6dba75d0..1dec9d511 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -86,6 +86,8 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { } TopologyDialect topologyDialect = (TopologyDialect) dialect; + TopologyUtils topologyUtils = new TopologyUtils( + topologyDialect, this.clusterInstanceTemplate, this.initialHostSpec, this.pluginService.getHostSpecBuilder()); return this.servicesContainer.getMonitorService().runIfAbsent( ClusterTopologyMonitorImpl.class, this.clusterId, diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java index d8447ac07..a35c4498d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -146,4 +146,8 @@ public HostRole getHostRole(Connection conn) throws SQLException { throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole")); } + + HostSpec identifyConnection(Connection connection) throws SQLException { + + } } From 980ea01bead42a5abbaeed1c116690d9d868d085 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 08:08:09 -0700 Subject: [PATCH 06/46] Builds successfully --- .../amazon/jdbc/HostListProvider.java | 2 + .../jdbc/dialect/AuroraDialectUtils.java | 12 - .../jdbc/dialect/AuroraMysqlDialect.java | 23 +- .../amazon/jdbc/dialect/AuroraPgDialect.java | 21 +- .../jdbc/dialect/MultiAzDialectUtils.java | 6 +- .../RdsMultiAzDbClusterMysqlDialect.java | 32 +-- .../dialect/RdsMultiAzDbClusterPgDialect.java | 39 ++- .../amazon/jdbc/dialect/TopologyDialect.java | 8 +- .../AuroraHostListProvider.java | 14 +- .../hostlistprovider/RdsHostListProvider.java | 234 ++++-------------- .../RdsMultiAzDbClusterListProvider.java | 182 +------------- .../ClusterTopologyMonitorImpl.java | 39 ++- .../MonitoringRdsHostListProvider.java | 17 +- .../amazon/jdbc/util/TopologyUtils.java | 32 ++- .../aurora/TestAuroraHostListProvider.java | 5 +- .../RdsHostListProviderTest.java | 13 +- .../RdsMultiAzDbClusterListProviderTest.java | 17 +- 17 files changed, 172 insertions(+), 524 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java index 0aa93714a..1a147658f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java @@ -19,6 +19,7 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; public interface HostListProvider { @@ -40,6 +41,7 @@ public interface HostListProvider { */ HostRole getHostRole(Connection connection) throws SQLException; + @Nullable HostSpec identifyConnection(Connection connection) throws SQLException; String getClusterId() throws UnsupportedOperationException, SQLException; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java index 0c8c0221c..647e93cc2 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -95,16 +95,4 @@ public boolean isWriterInstance(final Connection connection) throws SQLException return false; } - - // Returns a writer node ID if connected to a writer node. Returns null otherwise. - protected String getWriterNodeId(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } - return null; - } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 7c58d8b36..c4c22b2eb 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -91,14 +91,8 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - IS_WRITER_QUERY); + return (properties, initialUrl, servicesContainer) -> + new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } @Override @@ -141,18 +135,13 @@ public boolean isWriterInstance(Connection connection) throws SQLException { } @Override - public HostSpec identifyConnection(Connection connection) throws SQLException { - return null; - } - - @Override - public HostRole getHostRole(Connection conn) throws SQLException { - return null; + public String getIsReaderQuery() { + return IS_READER_QUERY; } @Override - public String getIsReaderQuery() { - return ""; + public String getInstanceIdQuery() { + return NODE_ID_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 3f8a573d2..191ac849f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.util.DriverInfo; @@ -141,14 +142,8 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - IS_WRITER_QUERY); + return (properties, initialUrl, servicesContainer) -> + new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } @Override @@ -156,6 +151,16 @@ public String getTopologyQuery() { return TOPOLOGY_QUERY; } + @Override + public String getIsReaderQuery() { + return IS_READER_QUERY; + } + + @Override + public String getInstanceIdQuery() { + return NODE_ID_QUERY; + } + @Override @Nullable public String getWriterId(final Connection connection) { // The Aurora topology query can detect the writer without a suggested writer ID, so we intentionally return null. diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java index 3d885033f..3a3075429 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -70,19 +70,19 @@ protected TopologyQueryHostSpec createHost( return new TopologyQueryHostSpec(instanceName, isWriter, 0, Timestamp.from(Instant.now())); } - public @Nullable String getSuggestedWriterId(Connection connection) throws SQLException { + public @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { if (resultSet.next()) { String writerId = resultSet.getString(this.writerIdQueryColumn); if (!StringUtils.isNullOrEmpty(writerId)) { - // Replica status exists and shows a writer instance ID, which means that this instance is a reader. return writerId; } } } - // Replica status doesn't exist, which means that this instance is a writer. + // Replica status doesn't exist, which means that this instance is a writer. We execute instanceIdQuery to get the + // ID of this writer instance. try (final ResultSet resultSet = stmt.executeQuery(this.instanceIdQuery)) { if (resultSet.next()) { return resultSet.getString(1); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index 439135aa8..d66ae416c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -100,26 +100,10 @@ public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - FETCH_WRITER_NODE_QUERY, - FETCH_WRITER_NODE_QUERY_COLUMN_NAME); + return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } else { - return new RdsMultiAzDbClusterListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - FETCH_WRITER_NODE_QUERY, - FETCH_WRITER_NODE_QUERY_COLUMN_NAME); + return new RdsMultiAzDbClusterListProvider(this, properties, initialUrl, servicesContainer); } }; } @@ -153,11 +137,21 @@ public List processQueryResults(ResultSet rs, @Nullable S @Override public @Nullable String getWriterId(Connection connection) throws SQLException { - return dialectUtils.getSuggestedWriterId(connection); + return dialectUtils.getWriterId(connection); } @Override public boolean isWriterInstance(Connection connection) throws SQLException { return dialectUtils.isWriterInstance(connection); } + + @Override + public String getIsReaderQuery() { + return IS_READER_QUERY; + } + + @Override + public String getInstanceIdQuery() { + return NODE_ID_QUERY; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index fd0664c86..2188bec11 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -20,7 +20,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import java.util.Collections; import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; @@ -28,6 +27,7 @@ import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; import software.amazon.jdbc.hostlistprovider.RdsMultiAzDbClusterListProvider; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; @@ -87,27 +87,9 @@ public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - FETCH_WRITER_NODE_QUERY, - FETCH_WRITER_NODE_QUERY_COLUMN_NAME); - + return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } else { - - return new RdsMultiAzDbClusterListProvider( - properties, - initialUrl, - servicesContainer, - TOPOLOGY_QUERY, - NODE_ID_QUERY, - IS_READER_QUERY, - FETCH_WRITER_NODE_QUERY, - FETCH_WRITER_NODE_QUERY_COLUMN_NAME); + return new RdsMultiAzDbClusterListProvider(this, properties, initialUrl, servicesContainer); } }; } @@ -119,7 +101,7 @@ public String getTopologyQuery() { @Override public @Nullable String getWriterId(final Connection connection) throws SQLException { - return dialectUtils.getSuggestedWriterId(connection); + return dialectUtils.getWriterId(connection); } @Override @@ -128,7 +110,18 @@ public boolean isWriterInstance(Connection connection) throws SQLException { } @Override - public @Nullable List processQueryResults(ResultSet rs, String suggestedWriterId) throws SQLException { + public String getIsReaderQuery() { + return IS_READER_QUERY; + } + + @Override + public String getInstanceIdQuery() { + return NODE_ID_QUERY; + } + + @Override + public @Nullable List processQueryResults(ResultSet rs, String suggestedWriterId) + throws SQLException { return this.dialectUtils.processQueryResults(rs, suggestedWriterId); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index 98d421c17..928309741 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -21,10 +21,8 @@ import java.sql.SQLException; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; -public interface TopologyDialect { +public interface TopologyDialect extends Dialect { String getTopologyQuery(); @Nullable @@ -37,9 +35,7 @@ List processQueryResults(ResultSet rs, @Nullable String s // TODO: can we remove this and use getHostRole instead? boolean isWriterInstance(final Connection connection) throws SQLException; - HostSpec identifyConnection(Connection connection) throws SQLException - String getIsReaderQuery(); - String get + String getInstanceIdQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java index fc53f9e1d..64ee0961c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java @@ -19,6 +19,7 @@ import java.util.Properties; import java.util.logging.Logger; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.FullServicesContainer; @@ -27,17 +28,10 @@ public class AuroraHostListProvider extends RdsHostListProvider { static final Logger LOGGER = Logger.getLogger(AuroraHostListProvider.class.getName()); public AuroraHostListProvider( + final TopologyDialect dialect, final Properties properties, final String originalUrl, - final FullServicesContainer servicesContainer, - final String topologyQuery, - final String nodeIdQuery, - final String isReaderQuery) { - super(properties, - originalUrl, - servicesContainer, - topologyQuery, - nodeIdQuery, - isReaderQuery); + final FullServicesContainer servicesContainer) { + super(dialect, properties, originalUrl, servicesContainer); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 89824556d..65054208b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -17,16 +17,9 @@ package software.amazon.jdbc.hostlistprovider; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLSyntaxErrorException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -37,7 +30,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; -import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.AwsWrapperProperty; @@ -46,7 +38,7 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; @@ -54,6 +46,7 @@ import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.SynchronousExecutor; +import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; @@ -92,9 +85,13 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected static final CacheMap suggestedPrimaryClusterIdCache = new CacheMap<>(); protected static final CacheMap primaryClusterIdCache = new CacheMap<>(); + protected final ReentrantLock lock = new ReentrantLock(); + protected final TopologyDialect dialect; + protected final Properties properties; + protected final String originalUrl; protected final FullServicesContainer servicesContainer; protected final HostListProviderService hostListProviderService; - protected final String originalUrl; + protected RdsUrlType rdsUrlType; protected long refreshRateNano = CLUSTER_TOPOLOGY_REFRESH_RATE_MS.defaultValue != null ? TimeUnit.MILLISECONDS.toNanos(Long.parseLong(CLUSTER_TOPOLOGY_REFRESH_RATE_MS.defaultValue)) @@ -102,10 +99,9 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected List hostList = new ArrayList<>(); protected List initialHostList = new ArrayList<>(); protected HostSpec initialHostSpec; - - protected final ReentrantLock lock = new ReentrantLock(); protected String clusterId; protected HostSpec clusterInstanceTemplate; + protected TopologyUtils topologyUtils; // A primary clusterId is a clusterId that is based off of a cluster endpoint URL // (rather than a GUID or a value provided by the user). @@ -113,16 +109,16 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected volatile boolean isInitialized = false; - protected Properties properties; - static { PropertyDefinition.registerPluginProperties(RdsHostListProvider.class); } public RdsHostListProvider( + final TopologyDialect dialect, final Properties properties, final String originalUrl, final FullServicesContainer servicesContainer) { + this.dialect = dialect; this.properties = properties; this.originalUrl = originalUrl; this.servicesContainer = servicesContainer; @@ -200,6 +196,12 @@ protected void init() throws SQLException { } } + this.topologyUtils = new TopologyUtils( + this.dialect, + this.clusterInstanceTemplate, + this.initialHostSpec, + this.servicesContainer.getPluginService().getHostSpecBuilder()); + this.isInitialized = true; } finally { lock.unlock(); @@ -251,7 +253,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f } // fetch topology from the DB - final List hosts = queryForTopology(conn); + final List hosts = this.topologyUtils.queryForTopology(conn); if (!Utils.isNullOrEmpty(hosts)) { this.servicesContainer.getStorageService().set(this.clusterId, new Topology(hosts)); @@ -274,7 +276,7 @@ protected void clusterIdChanged(final String oldClusterId) throws SQLException { // do nothing } - protected ClusterSuggestedResult getSuggestedClusterId(final String url) { + protected @Nullable ClusterSuggestedResult getSuggestedClusterId(final String url) { Map entries = this.servicesContainer.getStorageService().getEntries(Topology.class); if (entries == null) { return null; @@ -288,9 +290,7 @@ protected ClusterSuggestedResult getSuggestedClusterId(final String url) { if (key.equals(url)) { return new ClusterSuggestedResult(url, isPrimaryCluster); } - if (hosts == null) { - continue; - } + for (final HostSpec host : hosts) { if (host.getHostAndPort().equals(url)) { LOGGER.finest(() -> Messages.get("RdsHostListProvider.suggestedClusterId", @@ -345,131 +345,7 @@ protected void suggestPrimaryCluster(final @NonNull List primaryCluste * @throws SQLException if errors occurred while retrieving the topology. */ protected List queryForTopology(final Connection conn) throws SQLException { - int networkTimeout = -1; - try { - networkTimeout = conn.getNetworkTimeout(); - // The topology query is not monitored by the EFM plugin, so it needs a socket timeout - if (networkTimeout == 0) { - conn.setNetworkTimeout(networkTimeoutExecutor, defaultTopologyQueryTimeoutMs); - } - } catch (SQLException e) { - LOGGER.warning(() -> Messages.get("RdsHostListProvider.errorGettingNetworkTimeout", - new Object[] {e.getMessage()})); - } - - try (final Statement stmt = conn.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.topologyQuery)) { - return processQueryResults(resultSet); - } catch (final SQLSyntaxErrorException e) { - throw new SQLException(Messages.get("RdsHostListProvider.invalidQuery"), e); - } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); - } - } - } - - /** - * Form a list of hosts from the results of the topology query. - * - * @param resultSet The results of the topology query - * @return a list of {@link HostSpec} objects representing - * the topology that was returned by the - * topology query. The list will be empty if the topology query returned an invalid topology - * (no writer instance). - */ - private List processQueryResults(final ResultSet resultSet) throws SQLException { - - final HashMap hostMap = new HashMap<>(); - - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - while (resultSet.next()) { - final HostSpec host = createHost(resultSet); - hostMap.put(host.getHost(), host); - } - - final List hosts = new ArrayList<>(); - final List writers = new ArrayList<>(); - - for (final HostSpec host : hostMap.values()) { - if (host.getRole() != HostRole.WRITER) { - hosts.add(host); - } else { - writers.add(host); - } - } - - int writerCount = writers.size(); - - if (writerCount == 0) { - LOGGER.severe( - () -> Messages.get( - "RdsHostListProvider.invalidTopology")); - hosts.clear(); - } else if (writerCount == 1) { - hosts.add(writers.get(0)); - } else { - // Take the latest updated writer node as the current writer. All others will be ignored. - List sortedWriters = writers.stream() - .sorted(Comparator.comparing(HostSpec::getLastUpdateTime, Comparator.nullsLast(Comparator.reverseOrder()))) - .collect(Collectors.toList()); - hosts.add(sortedWriters.get(0)); - } - - return hosts; - } - - /** - * Creates an instance of HostSpec which captures details about a connectable host. - * - * @param resultSet the result set from querying the topology - * @return a {@link HostSpec} instance for a specific instance from the cluster - * @throws SQLException If unable to retrieve the hostName from the result set - */ - protected HostSpec createHost(final ResultSet resultSet) throws SQLException { - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), CPU utilization, node lag in time. - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final double cpuUtilization = resultSet.getDouble(3); - final double nodeLag = resultSet.getDouble(4); - Timestamp lastUpdateTime; - try { - lastUpdateTime = resultSet.getTimestamp(5); - } catch (Exception e) { - lastUpdateTime = Timestamp.from(Instant.now()); - } - - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); - - return createHost(hostName, isWriter, weight, lastUpdateTime); - } - - protected HostSpec createHost( - String host, - final boolean isWriter, - final long weight, - final Timestamp lastUpdateTime) { - - host = host == null ? "?" : host; - final String endpoint = getHostEndpoint(host); - final int port = this.clusterInstanceTemplate.isPortSpecified() - ? this.clusterInstanceTemplate.getPort() - : this.initialHostSpec.getPort(); - - final HostSpec hostSpec = this.hostListProviderService.getHostSpecBuilder() - .host(endpoint) - .port(port) - .role(isWriter ? HostRole.WRITER : HostRole.READER) - .availability(HostAvailability.AVAILABLE) - .weight(weight) - .lastUpdateTime(lastUpdateTime) - .build(); - hostSpec.addAlias(host); - hostSpec.setHostId(host); - return hostSpec; + return this.topologyUtils.queryForTopology(conn); } /** @@ -593,64 +469,52 @@ public FetchTopologyResult(final boolean isCachedData, final List host @Override public HostRole getHostRole(Connection conn) throws SQLException { - try (final Statement stmt = conn.createStatement(); - final ResultSet rs = stmt.executeQuery(this.isReaderQuery)) { - if (rs.next()) { - boolean isReader = rs.getBoolean(1); - return isReader ? HostRole.READER : HostRole.WRITER; - } - } catch (SQLException e) { - throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole"), e); - } - - throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole")); + return this.topologyUtils.getHostRole(conn); } @Override - public HostSpec identifyConnection(Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.nodeIdQuery)) { - if (resultSet.next()) { - final String instanceName = resultSet.getString(1); + public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { + // TODO: why do we return null in some unexpected scenarios and throw an exception in others? + try { + String instanceId = this.topologyUtils.getInstanceId(connection); + if (instanceId == null) { + throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection")); + } - List topology = this.refresh(connection); + List topology = this.refresh(connection); + boolean isForcedRefresh = false; + if (topology == null) { + topology = this.forceRefresh(connection); + isForcedRefresh = true; + } - boolean isForcedRefresh = false; - if (topology == null) { - topology = this.forceRefresh(connection); - isForcedRefresh = true; - } + if (topology == null) { + return null; + } + HostSpec foundHost = topology + .stream() + .filter(host -> Objects.equals(instanceId, host.getHostId())) + .findAny() + .orElse(null); + + if (foundHost == null && !isForcedRefresh) { + topology = this.forceRefresh(connection); if (topology == null) { return null; } - HostSpec foundHost = topology + foundHost = topology .stream() - .filter(host -> Objects.equals(instanceName, host.getHostId())) + .filter(host -> Objects.equals(instanceId, host.getHostId())) .findAny() .orElse(null); - - if (foundHost == null && !isForcedRefresh) { - topology = this.forceRefresh(connection); - if (topology == null) { - return null; - } - - foundHost = topology - .stream() - .filter(host -> Objects.equals(instanceName, host.getHostId())) - .findAny() - .orElse(null); - } - - return foundHost; } + + return foundHost; } catch (final SQLException e) { throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection"), e); } - - throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection")); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java index a63323176..8b134fd45 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java @@ -16,193 +16,19 @@ package software.amazon.jdbc.hostlistprovider; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLSyntaxErrorException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; import java.util.Properties; import java.util.logging.Logger; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.Messages; public class RdsMultiAzDbClusterListProvider extends RdsHostListProvider { - private final String fetchWriterNodeQuery; - private final String fetchWriterNodeQueryHeader; static final Logger LOGGER = Logger.getLogger(RdsMultiAzDbClusterListProvider.class.getName()); public RdsMultiAzDbClusterListProvider( + final TopologyDialect dialect, final Properties properties, final String originalUrl, - final FullServicesContainer servicesContainer, - final String topologyQuery, - final String nodeIdQuery, - final String isReaderQuery, - final String fetchWriterNodeQuery, - final String fetchWriterNodeQueryHeader - ) { - super(properties, - originalUrl, - servicesContainer, - topologyQuery, - nodeIdQuery, - isReaderQuery); - this.fetchWriterNodeQuery = fetchWriterNodeQuery; - this.fetchWriterNodeQueryHeader = fetchWriterNodeQueryHeader; - } - - /** - * Obtain a cluster topology from database. - * - * @param conn A connection to database to fetch the latest topology. - * @return a list of {@link HostSpec} objects representing the topology - * @throws SQLException if errors occurred while retrieving the topology. - */ - protected List queryForTopology(final Connection conn) throws SQLException { - int networkTimeout = -1; - try { - networkTimeout = conn.getNetworkTimeout(); - // The topology query is not monitored by the EFM plugin, so it needs a socket timeout - if (networkTimeout == 0) { - conn.setNetworkTimeout(networkTimeoutExecutor, defaultTopologyQueryTimeoutMs); - } - } catch (SQLException e) { - LOGGER.warning(() -> Messages.get("RdsHostListProvider.errorGettingNetworkTimeout", - new Object[] {e.getMessage()})); - } - - try { - final Statement stmt = conn.createStatement(); - String writerNodeId = processWriterNodeId(stmt.executeQuery(this.fetchWriterNodeQuery)); - if (writerNodeId == null) { - final ResultSet nodeIdResultSet = stmt.executeQuery(this.nodeIdQuery); - while (nodeIdResultSet.next()) { - writerNodeId = nodeIdResultSet.getString(1); - } - } - final ResultSet topologyResultSet = stmt.executeQuery(this.topologyQuery); - return processTopologyQueryResults(topologyResultSet, writerNodeId); - } catch (final SQLSyntaxErrorException e) { - throw new SQLException(Messages.get("RdsHostListProvider.invalidQuery"), e); - } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); - } - } - } - - /** - * Get writer node ID. - * - * @param fetchWriterNodeResultSet A ResultSet of writer node query - * @return String The ID of a writer node - * @throws SQLException if errors occurred while retrieving the topology - */ - private String processWriterNodeId(final ResultSet fetchWriterNodeResultSet) throws SQLException { - String writerNodeId = null; - if (fetchWriterNodeResultSet.next()) { - writerNodeId = fetchWriterNodeResultSet.getString(fetchWriterNodeQueryHeader); - } - return writerNodeId; - } - - /** - * Form a list of hosts from the results of the topology query. - * - * @param topologyResultSet The results of the topology query - * @param writerNodeId The writer node ID - * @return a list of {@link HostSpec} objects representing - * the topology that was returned by the - * topology query. The list will be empty if the topology query returned an invalid topology - * (no writer instance). - */ - private List processTopologyQueryResults( - final ResultSet topologyResultSet, - final String writerNodeId) throws SQLException { - - final HashMap hostMap = new HashMap<>(); - - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - while (topologyResultSet.next()) { - final HostSpec host = createHost(topologyResultSet, writerNodeId); - hostMap.put(host.getHost(), host); - } - - final List hosts = new ArrayList<>(); - final List writers = new ArrayList<>(); - - for (final HostSpec host : hostMap.values()) { - if (host.getRole() != HostRole.WRITER) { - hosts.add(host); - } else { - writers.add(host); - } - } - - int writerCount = writers.size(); - - if (writerCount == 0) { - LOGGER.severe(() -> Messages.get("RdsHostListProvider.invalidTopology")); - hosts.clear(); - } else { - hosts.add(writers.get(0)); - } - - return hosts; - } - - /** - * Creates an instance of HostSpec which captures details about a connectable host. - * - * @param resultSet the result set from querying the topology - * @return a {@link HostSpec} instance for a specific instance from the cluster - * @throws SQLException If unable to retrieve the hostName from the result set - */ - private HostSpec createHost(final ResultSet resultSet, final String writerNodeId) throws SQLException { - - String hostName = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" - String instanceName = hostName.substring(0, hostName.indexOf(".")); // "instance-name" - - // "instance-name.XYZ.us-west-2.rds.amazonaws.com" based on cluster instance template - final String endpoint = getHostEndpoint(instanceName); - - String hostId = resultSet.getString("id"); - int queryPort = resultSet.getInt("port"); - final int port = this.clusterInstanceTemplate.isPortSpecified() - ? this.clusterInstanceTemplate.getPort() - : queryPort; - final boolean isWriter = hostId.equals(writerNodeId); - - final HostSpec hostSpec = this.hostListProviderService.getHostSpecBuilder() - .host(endpoint) - .hostId(hostId) - .port(port) - .role(isWriter ? HostRole.WRITER : HostRole.READER) - .availability(HostAvailability.AVAILABLE) - .weight(0) - .lastUpdateTime(Timestamp.from(Instant.now())) - .build(); - hostSpec.addAlias(hostName); - return hostSpec; - } - - /** - * Build a host dns endpoint based on host/node name. - * - * @param nodeName A host name. - * @return Host dns endpoint - */ - protected String getHostEndpoint(final String nodeName) { - final String host = this.clusterInstanceTemplate.getHost(); - return host.replace("?", nodeName); + final FullServicesContainer servicesContainer) { + super(dialect, properties, originalUrl, servicesContainer); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 0f763659d..7ab890ef5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -39,7 +39,6 @@ import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.util.ExecutorFactory; @@ -62,10 +61,7 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected static final Executor networkTimeoutExecutor = new SynchronousExecutor(); protected static final RdsUtils rdsHelper = new RdsUtils(); protected static final long monitorTerminationTimeoutSec = 30; - - protected static final int defaultTopologyQueryTimeoutMs = 1000; protected static final int closeConnectionNetworkTimeoutMs = 500; - protected static final int defaultConnectionTimeoutMs = 5000; protected static final int defaultSocketTimeoutMs = 5000; @@ -73,25 +69,14 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected static final long highRefreshPeriodAfterPanicNano = TimeUnit.SECONDS.toNanos(30); protected static final long ignoreTopologyRequestNano = TimeUnit.SECONDS.toNanos(10); - protected final long refreshRateNano; - protected final long highRefreshRateNano; - protected final TopologyUtils topologyUtils; - protected final FullServicesContainer servicesContainer; - protected final Properties properties; - protected final Properties monitoringProperties; - protected final HostSpec initialHostSpec; - protected final HostSpec clusterInstanceTemplate; - - protected String clusterId; protected final AtomicReference writerHostSpec = new AtomicReference<>(null); protected final AtomicReference monitoringConnection = new AtomicReference<>(null); - protected boolean isVerifiedWriterConnection = false; - protected long highRefreshRateEndTimeNano = 0; + protected final Object topologyUpdated = new Object(); protected final AtomicBoolean requestToUpdateTopology = new AtomicBoolean(false); protected final AtomicLong ignoreNewTopologyRequestsEndTimeNano = new AtomicLong(-1); protected final ConcurrentHashMap submittedNodes = new ConcurrentHashMap<>(); - protected ExecutorService nodeExecutorService = null; + protected final ReentrantLock nodeExecutorLock = new ReentrantLock(); protected final AtomicBoolean nodeThreadsStop = new AtomicBoolean(false); protected final AtomicReference nodeThreadsWriterConnection = new AtomicReference<>(null); @@ -99,6 +84,20 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected final AtomicReference nodeThreadsReaderConnection = new AtomicReference<>(null); protected final AtomicReference> nodeThreadsLatestTopology = new AtomicReference<>(null); + protected final long refreshRateNano; + protected final long highRefreshRateNano; + protected final TopologyUtils topologyUtils; + protected final FullServicesContainer servicesContainer; + protected final Properties properties; + protected final Properties monitoringProperties; + protected final HostSpec initialHostSpec; + protected final HostSpec clusterInstanceTemplate; + + protected ExecutorService nodeExecutorService = null; + protected boolean isVerifiedWriterConnection = false; + protected long highRefreshRateEndTimeNano = 0; + protected String clusterId; + public ClusterTopologyMonitorImpl( final FullServicesContainer servicesContainer, final TopologyUtils topologyUtils, @@ -516,7 +515,7 @@ protected List openAnyConnectionAndUpdateTopology() { new Object[]{this.initialHostSpec.getHost()})); try { - if (this.dialect.isWriterInstance(this.monitoringConnection.get())) { + if (this.topologyUtils.isWriterInstance(this.monitoringConnection.get())) { this.isVerifiedWriterConnection = true; writerVerifiedByThisThread = true; @@ -527,7 +526,7 @@ protected List openAnyConnectionAndUpdateTopology() { "ClusterTopologyMonitorImpl.writerMonitoringConnection", new Object[]{this.writerHostSpec.get().getHost()})); } else { - final String nodeId = this.dialect.getInstanceId(this.monitoringConnection.get()); + final String nodeId = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); if (!StringUtils.isNullOrEmpty(nodeId)) { this.writerHostSpec.set(this.createHost(nodeId, true, 0, null)); LOGGER.finest( @@ -710,7 +709,7 @@ public void run() { boolean isWriter = false; try { - isWriter = this.dialect.isWriterInstance(connection); + isWriter = this.monitor.topologyUtils.isWriterInstance(connection); } catch (SQLSyntaxErrorException ex) { LOGGER.severe(() -> Messages.get("NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index 1dec9d511..be4aa66d9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -29,13 +29,10 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.cleanup.CanReleaseResources; -import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.monitoring.MonitorService; import software.amazon.jdbc.util.storage.StorageService; @@ -59,10 +56,11 @@ public class MonitoringRdsHostListProvider extends RdsHostListProvider protected final long highRefreshRateNano; public MonitoringRdsHostListProvider( + final TopologyDialect dialect, final Properties properties, final String originalUrl, final FullServicesContainer servicesContainer) { - super(properties, originalUrl, servicesContainer); + super(dialect, properties, originalUrl, servicesContainer); this.servicesContainer = servicesContainer; this.pluginService = servicesContainer.getPluginService(); this.highRefreshRateNano = TimeUnit.MILLISECONDS.toNanos( @@ -79,15 +77,6 @@ protected void init() throws SQLException { } protected ClusterTopologyMonitor initMonitor() throws SQLException { - Dialect dialect = this.servicesContainer.getPluginService().getDialect(); - if (!(dialect instanceof TopologyDialect)) { - throw new SQLException( - Messages.get("TopologyUtils.topologyDialectRequired", new Object[]{dialect.getClass().getName()})); - } - - TopologyDialect topologyDialect = (TopologyDialect) dialect; - TopologyUtils topologyUtils = new TopologyUtils( - topologyDialect, this.clusterInstanceTemplate, this.initialHostSpec, this.pluginService.getHostSpecBuilder()); return this.servicesContainer.getMonitorService().runIfAbsent( ClusterTopologyMonitorImpl.class, this.clusterId, @@ -95,7 +84,7 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.properties, (servicesContainer) -> new ClusterTopologyMonitorImpl( this.servicesContainer, - topologyDialect, + this.topologyUtils, this.clusterId, this.initialHostSpec, this.properties, diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java index a35c4498d..59a494322 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Objects; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -47,14 +48,17 @@ public class TopologyUtils { protected final HostSpecBuilder hostSpecBuilder; public TopologyUtils( - TopologyDialect dialect, HostSpec clusterInstanceTemplate, HostSpec initialHostSpec, HostSpecBuilder hostSpecBuilder) { + TopologyDialect dialect, + HostSpec clusterInstanceTemplate, + HostSpec initialHostSpec, + HostSpecBuilder hostSpecBuilder) { this.dialect = dialect; this.clusterInstanceTemplate = clusterInstanceTemplate; this.initialHostSpec = initialHostSpec; this.hostSpecBuilder = hostSpecBuilder; } - public List queryForTopology(Connection conn) throws SQLException { + public @Nullable List queryForTopology(Connection conn) throws SQLException { int networkTimeout = -1; try { networkTimeout = conn.getNetworkTimeout(); @@ -133,9 +137,27 @@ protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { return hostSpec; } + public @Nullable String getInstanceId(final Connection connection) { + try { + try (final Statement stmt = connection.createStatement(); + final ResultSet resultSet = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { + if (resultSet.next()) { + return resultSet.getString(1); + } + } + } catch (SQLException ex) { + // do nothing + } + return null; + } + + public boolean isWriterInstance(Connection connection) throws SQLException { + return this.dialect.isWriterInstance(connection); + } + public HostRole getHostRole(Connection conn) throws SQLException { try (final Statement stmt = conn.createStatement(); - final ResultSet rs = stmt.executeQuery(this.isReaderQuery)) { + final ResultSet rs = stmt.executeQuery(this.dialect.getIsReaderQuery())) { if (rs.next()) { boolean isReader = rs.getBoolean(1); return isReader ? HostRole.READER : HostRole.WRITER; @@ -146,8 +168,4 @@ public HostRole getHostRole(Connection conn) throws SQLException { throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole")); } - - HostSpec identifyConnection(Connection connection) throws SQLException { - - } } diff --git a/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java b/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java index c35f6b0f8..d9ddbb706 100644 --- a/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java +++ b/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java @@ -17,14 +17,15 @@ package integration.container.aurora; import java.util.Properties; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; import software.amazon.jdbc.util.FullServicesContainer; public class TestAuroraHostListProvider extends AuroraHostListProvider { public TestAuroraHostListProvider( - FullServicesContainer servicesContainer, Properties properties, String originalUrl) { - super(properties, originalUrl, servicesContainer, "", "", ""); + FullServicesContainer servicesContainer, TopologyDialect dialect, Properties properties, String originalUrl) { + super(dialect, properties, originalUrl, servicesContainer); } public static void clearCache() { diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 797d151be..3574af62a 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -60,7 +60,7 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.dialect.Dialect; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; @@ -80,7 +80,7 @@ class RdsHostListProviderTest { @Mock private PluginService mockPluginService; @Mock private HostListProviderService mockHostListProviderService; @Mock private EventPublisher mockEventPublisher; - @Mock Dialect mockTopologyAwareDialect; + @Mock private TopologyDialect mockDialect; @Captor private ArgumentCaptor queryCaptor; private AutoCloseable closeable; @@ -101,7 +101,7 @@ void setUp() throws SQLException { when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); when(mockConnection.createStatement()).thenReturn(mockStatement); when(mockStatement.executeQuery(queryCaptor.capture())).thenReturn(mockResultSet); - when(mockHostListProviderService.getDialect()).thenReturn(mockTopologyAwareDialect); + when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); when(mockHostListProviderService.getHostSpecBuilder()) .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); when(mockHostListProviderService.getCurrentConnection()).thenReturn(mockConnection); @@ -115,11 +115,8 @@ void tearDown() throws Exception { } private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { - RdsHostListProvider provider = new RdsHostListProvider( - new Properties(), - originalUrl, - mockServicesContainer, - "foo", "bar", "baz"); + RdsHostListProvider provider = + new RdsHostListProvider(mockDialect, new Properties(), originalUrl, mockServicesContainer); provider.init(); return provider; } diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java index df6d6ee50..af3955e09 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java @@ -55,7 +55,7 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.dialect.Dialect; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; import software.amazon.jdbc.util.FullServicesContainer; @@ -74,7 +74,7 @@ class RdsMultiAzDbClusterListProviderTest { @Mock private PluginService mockPluginService; @Mock private HostListProviderService mockHostListProviderService; @Mock private EventPublisher mockEventPublisher; - @Mock Dialect mockTopologyAwareDialect; + @Mock private TopologyDialect mockDialect; @Captor private ArgumentCaptor queryCaptor; private AutoCloseable closeable; @@ -95,7 +95,7 @@ void setUp() throws SQLException { when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); when(mockConnection.createStatement()).thenReturn(mockStatement); when(mockStatement.executeQuery(queryCaptor.capture())).thenReturn(mockResultSet); - when(mockHostListProviderService.getDialect()).thenReturn(mockTopologyAwareDialect); + when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); when(mockHostListProviderService.getHostSpecBuilder()) .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); } @@ -108,15 +108,8 @@ void tearDown() throws Exception { } private RdsMultiAzDbClusterListProvider getRdsMazDbClusterHostListProvider(String originalUrl) throws SQLException { - RdsMultiAzDbClusterListProvider provider = new RdsMultiAzDbClusterListProvider( - new Properties(), - originalUrl, - mockServicesContainer, - "foo", - "bar", - "baz", - "fang", - "li"); + RdsMultiAzDbClusterListProvider provider = + new RdsMultiAzDbClusterListProvider(mockDialect, new Properties(), originalUrl, mockServicesContainer); provider.init(); // provider.clusterId = "cluster-id"; return provider; From 227eac964a7459daa55c4a938310623b68715251 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 08:16:34 -0700 Subject: [PATCH 07/46] Remove unnecessary classes --- .../RdsMultiAzDbClusterMysqlDialect.java | 4 +- .../dialect/RdsMultiAzDbClusterPgDialect.java | 4 +- .../AuroraHostListProvider.java | 37 -- .../RdsMultiAzDbClusterListProvider.java | 34 -- .../aurora/TestAuroraHostListProvider.java | 34 -- .../tests/AdvancedPerformanceTest.java | 4 +- .../container/tests/AutoscalingTests.java | 6 +- .../container/tests/FailoverTest.java | 4 +- .../container/tests/PerformanceTest.java | 8 +- .../tests/ReadWriteSplittingTests.java | 6 +- .../RdsMultiAzDbClusterListProviderTest.java | 463 ------------------ .../FailoverConnectionPluginTest.java | 4 +- 12 files changed, 20 insertions(+), 588 deletions(-) delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java delete mode 100644 wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java delete mode 100644 wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index d66ae416c..da55b460a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -27,7 +27,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.RdsMultiAzDbClusterListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverRestriction; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; @@ -103,7 +103,7 @@ public HostListProviderSupplier getHostListProvider() { return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } else { - return new RdsMultiAzDbClusterListProvider(this, properties, initialUrl, servicesContainer); + return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); } }; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index 2188bec11..14041d849 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -26,7 +26,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; -import software.amazon.jdbc.hostlistprovider.RdsMultiAzDbClusterListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; @@ -89,7 +89,7 @@ public HostListProviderSupplier getHostListProvider() { if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); } else { - return new RdsMultiAzDbClusterListProvider(this, properties, initialUrl, servicesContainer); + return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); } }; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java deleted file mode 100644 index 64ee0961c..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraHostListProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider; - - -import java.util.Properties; -import java.util.logging.Logger; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.util.FullServicesContainer; - - -public class AuroraHostListProvider extends RdsHostListProvider { - - static final Logger LOGGER = Logger.getLogger(AuroraHostListProvider.class.getName()); - - public AuroraHostListProvider( - final TopologyDialect dialect, - final Properties properties, - final String originalUrl, - final FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java deleted file mode 100644 index 8b134fd45..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider; - -import java.util.Properties; -import java.util.logging.Logger; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.util.FullServicesContainer; - -public class RdsMultiAzDbClusterListProvider extends RdsHostListProvider { - static final Logger LOGGER = Logger.getLogger(RdsMultiAzDbClusterListProvider.class.getName()); - - public RdsMultiAzDbClusterListProvider( - final TopologyDialect dialect, - final Properties properties, - final String originalUrl, - final FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); - } -} diff --git a/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java b/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java deleted file mode 100644 index d9ddbb706..000000000 --- a/wrapper/src/test/java/integration/container/aurora/TestAuroraHostListProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 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. - * 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 integration.container.aurora; - -import java.util.Properties; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; -import software.amazon.jdbc.util.FullServicesContainer; - -public class TestAuroraHostListProvider extends AuroraHostListProvider { - - public TestAuroraHostListProvider( - FullServicesContainer servicesContainer, TopologyDialect dialect, Properties properties, String originalUrl) { - super(dialect, properties, originalUrl, servicesContainer); - } - - public static void clearCache() { - AuroraHostListProvider.clearAll(); - } -} diff --git a/wrapper/src/test/java/integration/container/tests/AdvancedPerformanceTest.java b/wrapper/src/test/java/integration/container/tests/AdvancedPerformanceTest.java index 38087f2dc..7af9f613c 100644 --- a/wrapper/src/test/java/integration/container/tests/AdvancedPerformanceTest.java +++ b/wrapper/src/test/java/integration/container/tests/AdvancedPerformanceTest.java @@ -29,7 +29,6 @@ import integration.container.ConnectionStringHelper; import integration.container.TestDriverProvider; import integration.container.TestEnvironment; -import integration.container.aurora.TestAuroraHostListProvider; import integration.container.aurora.TestPluginServiceImpl; import integration.container.condition.DisableOnTestFeature; import integration.container.condition.EnableOnTestFeature; @@ -66,6 +65,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.Arguments; import software.amazon.jdbc.PropertyDefinition; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.plugin.efm.HostMonitorThreadContainer; import software.amazon.jdbc.plugin.efm2.HostMonitorServiceImpl; import software.amazon.jdbc.plugin.failover.FailoverSuccessSQLException; @@ -686,7 +686,7 @@ private void ensureClusterHealthy() throws InterruptedException { auroraUtil.makeSureInstancesUp(TimeUnit.MINUTES.toSeconds(5)); - TestAuroraHostListProvider.clearCache(); + RdsHostListProvider.clearAll(); TestPluginServiceImpl.clearHostAvailabilityCache(); HostMonitorThreadContainer.releaseInstance(); HostMonitorServiceImpl.closeAllMonitors(); diff --git a/wrapper/src/test/java/integration/container/tests/AutoscalingTests.java b/wrapper/src/test/java/integration/container/tests/AutoscalingTests.java index 307e00bb9..28a6c63e4 100644 --- a/wrapper/src/test/java/integration/container/tests/AutoscalingTests.java +++ b/wrapper/src/test/java/integration/container/tests/AutoscalingTests.java @@ -52,7 +52,7 @@ import software.amazon.jdbc.HikariPoolConfigurator; import software.amazon.jdbc.HikariPooledConnectionProvider; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverSuccessSQLException; import software.amazon.jdbc.plugin.readwritesplitting.ReadWriteSplittingPlugin; @@ -104,7 +104,7 @@ public void test_pooledConnectionAutoScaling_setReadOnlyOnOldConnection() final Properties props = getProps(); final long topologyRefreshRateMs = 5000; ReadWriteSplittingPlugin.READER_HOST_SELECTOR_STRATEGY.set(props, "leastConnections"); - AuroraHostListProvider.CLUSTER_TOPOLOGY_REFRESH_RATE_MS.set(props, + RdsHostListProvider.CLUSTER_TOPOLOGY_REFRESH_RATE_MS.set(props, Long.toString(topologyRefreshRateMs)); final TestEnvironmentInfo testInfo = TestEnvironment.getCurrent().getInfo(); @@ -186,7 +186,7 @@ public void test_pooledConnectionAutoScaling_failoverFromDeletedReader() final Properties props = getPropsWithFailover(); final long topologyRefreshRateMs = 5000; ReadWriteSplittingPlugin.READER_HOST_SELECTOR_STRATEGY.set(props, "leastConnections"); - AuroraHostListProvider.CLUSTER_TOPOLOGY_REFRESH_RATE_MS.set(props, + RdsHostListProvider.CLUSTER_TOPOLOGY_REFRESH_RATE_MS.set(props, Long.toString(topologyRefreshRateMs)); final TestEnvironmentInfo testInfo = TestEnvironment.getCurrent().getInfo(); diff --git a/wrapper/src/test/java/integration/container/tests/FailoverTest.java b/wrapper/src/test/java/integration/container/tests/FailoverTest.java index 347264561..7803ed185 100644 --- a/wrapper/src/test/java/integration/container/tests/FailoverTest.java +++ b/wrapper/src/test/java/integration/container/tests/FailoverTest.java @@ -62,7 +62,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.ds.AwsWrapperDataSource; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverSuccessSQLException; import software.amazon.jdbc.plugin.failover.TransactionStateUnknownSQLException; import software.amazon.jdbc.util.SqlState; @@ -688,7 +688,7 @@ protected Properties initDefaultProxiedProps() { // Some tests temporarily disable connectivity for 5 seconds. The socket timeout needs to be less than this to // trigger driver failover. PropertyDefinition.SOCKET_TIMEOUT.set(props, "2000"); - AuroraHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set( + RdsHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set( props, "?." + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointSuffix() + ":" + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointPort()); diff --git a/wrapper/src/test/java/integration/container/tests/PerformanceTest.java b/wrapper/src/test/java/integration/container/tests/PerformanceTest.java index 73e0b338c..a1c1bcd72 100644 --- a/wrapper/src/test/java/integration/container/tests/PerformanceTest.java +++ b/wrapper/src/test/java/integration/container/tests/PerformanceTest.java @@ -61,7 +61,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.provider.Arguments; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.OpenedConnectionTracker; @@ -149,7 +149,7 @@ public void test_FailureDetectionTime_EnhancedMonitoringEnabled(final String efm OpenedConnectionTracker.clearCache(); HostMonitorThreadContainer.releaseInstance(); HostMonitorServiceImpl.closeAllMonitors(); - AuroraHostListProvider.clearAll(); + RdsHostListProvider.clearAll(); MonitoringRdsHostListProvider.clearCache(); enhancedFailureMonitoringPerfDataList.clear(); @@ -231,7 +231,7 @@ public void test_FailureDetectionTime_FailoverAndEnhancedMonitoringEnabled(final OpenedConnectionTracker.clearCache(); HostMonitorThreadContainer.releaseInstance(); HostMonitorServiceImpl.closeAllMonitors(); - AuroraHostListProvider.clearAll(); + RdsHostListProvider.clearAll(); MonitoringRdsHostListProvider.clearCache(); failoverWithEfmPerfDataList.clear(); @@ -319,7 +319,7 @@ private void test_FailoverTime_SocketTimeout(final String plugins) throws IOExce OpenedConnectionTracker.clearCache(); HostMonitorThreadContainer.releaseInstance(); HostMonitorServiceImpl.closeAllMonitors(); - AuroraHostListProvider.clearAll(); + RdsHostListProvider.clearAll(); MonitoringRdsHostListProvider.clearCache(); failoverWithSocketTimeoutPerfDataList.clear(); diff --git a/wrapper/src/test/java/integration/container/tests/ReadWriteSplittingTests.java b/wrapper/src/test/java/integration/container/tests/ReadWriteSplittingTests.java index b8c96edbe..8b3accba1 100644 --- a/wrapper/src/test/java/integration/container/tests/ReadWriteSplittingTests.java +++ b/wrapper/src/test/java/integration/container/tests/ReadWriteSplittingTests.java @@ -73,7 +73,7 @@ import software.amazon.jdbc.HikariPooledConnectionProvider; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverFailedSQLException; import software.amazon.jdbc.plugin.failover.FailoverSuccessSQLException; @@ -122,7 +122,7 @@ public void tearDownEach() { protected static Properties getProxiedPropsWithFailover() { final Properties props = getPropsWithFailover(); - AuroraHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set(props, + RdsHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set(props, "?." + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointSuffix() + ":" + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointPort()); return props; @@ -130,7 +130,7 @@ protected static Properties getProxiedPropsWithFailover() { protected static Properties getProxiedProps() { final Properties props = getProps(); - AuroraHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set(props, + RdsHostListProvider.CLUSTER_INSTANCE_HOST_PATTERN.set(props, "?." + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointSuffix() + ":" + TestEnvironment.getCurrent().getInfo().getProxyDatabaseInfo().getInstanceEndpointPort()); return props; diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java deleted file mode 100644 index af3955e09..000000000 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsMultiAzDbClusterListProviderTest.java +++ /dev/null @@ -1,463 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atMostOnce; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLSyntaxErrorException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.HostListProviderService; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.HostSpecBuilder; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; -import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.events.EventPublisher; -import software.amazon.jdbc.util.storage.StorageService; -import software.amazon.jdbc.util.storage.TestStorageServiceImpl; - -class RdsMultiAzDbClusterListProviderTest { - private StorageService storageService; - private RdsMultiAzDbClusterListProvider rdsMazDbClusterHostListProvider; - - @Mock private Connection mockConnection; - @Mock private Statement mockStatement; - @Mock private ResultSet mockResultSet; - @Mock private FullServicesContainer mockServicesContainer; - @Mock private PluginService mockPluginService; - @Mock private HostListProviderService mockHostListProviderService; - @Mock private EventPublisher mockEventPublisher; - @Mock private TopologyDialect mockDialect; - @Captor private ArgumentCaptor queryCaptor; - - private AutoCloseable closeable; - private final HostSpec currentHostSpec = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("foo").port(1234).build(); - private final List hosts = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host1").build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host2").build()); - - @BeforeEach - void setUp() throws SQLException { - closeable = MockitoAnnotations.openMocks(this); - storageService = new TestStorageServiceImpl(mockEventPublisher); - when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); - when(mockServicesContainer.getStorageService()).thenReturn(storageService); - when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); - when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); - when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(queryCaptor.capture())).thenReturn(mockResultSet); - when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); - when(mockHostListProviderService.getHostSpecBuilder()) - .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); - } - - @AfterEach - void tearDown() throws Exception { - RdsMultiAzDbClusterListProvider.clearAll(); - storageService.clearAll(); - closeable.close(); - } - - private RdsMultiAzDbClusterListProvider getRdsMazDbClusterHostListProvider(String originalUrl) throws SQLException { - RdsMultiAzDbClusterListProvider provider = - new RdsMultiAzDbClusterListProvider(mockDialect, new Properties(), originalUrl, mockServicesContainer); - provider.init(); - // provider.clusterId = "cluster-id"; - return provider; - } - - @Test - void testGetTopology_returnCachedTopology() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("protocol://url/")); - final List expected = hosts; - storageService.set(rdsMazDbClusterHostListProvider.clusterId, new Topology(expected)); - - final FetchTopologyResult result = rdsMazDbClusterHostListProvider.getTopology(mockConnection, false); - assertEquals(expected, result.hosts); - assertEquals(2, result.hosts.size()); - verify(rdsMazDbClusterHostListProvider, never()).queryForTopology(mockConnection); - } - - @Test - void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - rdsMazDbClusterHostListProvider.isInitialized = true; - - storageService.set(rdsMazDbClusterHostListProvider.clusterId, new Topology(hosts)); - - final List newHosts = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); - doReturn(newHosts).when(rdsMazDbClusterHostListProvider).queryForTopology(mockConnection); - - final FetchTopologyResult result = rdsMazDbClusterHostListProvider.getTopology(mockConnection, true); - verify(rdsMazDbClusterHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertEquals(1, result.hosts.size()); - assertEquals(newHosts, result.hosts); - } - - @Test - void testGetTopology_noForceUpdate_queryReturnsEmptyHostList() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - rdsMazDbClusterHostListProvider.clusterId = "cluster-id"; - rdsMazDbClusterHostListProvider.isInitialized = true; - - final List expected = hosts; - storageService.set(rdsMazDbClusterHostListProvider.clusterId, new Topology(expected)); - - doReturn(new ArrayList<>()).when(rdsMazDbClusterHostListProvider).queryForTopology(mockConnection); - - final FetchTopologyResult result = rdsMazDbClusterHostListProvider.getTopology(mockConnection, false); - verify(rdsMazDbClusterHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertEquals(2, result.hosts.size()); - assertEquals(expected, result.hosts); - } - - @Test - void testGetTopology_withForceUpdate_returnsInitialHostList() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - rdsMazDbClusterHostListProvider.clear(); - - doReturn(new ArrayList<>()).when(rdsMazDbClusterHostListProvider).queryForTopology(mockConnection); - - final FetchTopologyResult result = rdsMazDbClusterHostListProvider.getTopology(mockConnection, true); - verify(rdsMazDbClusterHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertNotNull(result.hosts); - assertEquals( - Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("url").build()), - result.hosts); - } - - @Test - void testQueryForTopology_queryResultsInException() throws SQLException { - rdsMazDbClusterHostListProvider = getRdsMazDbClusterHostListProvider("protocol://url/"); - when(mockStatement.executeQuery(queryCaptor.capture())).thenThrow(new SQLSyntaxErrorException()); - - assertThrows( - SQLException.class, - () -> rdsMazDbClusterHostListProvider.queryForTopology(mockConnection)); - } - - @Test - void testGetCachedTopology_returnCachedTopology() throws SQLException { - rdsMazDbClusterHostListProvider = getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url"); - - final List expected = hosts; - storageService.set(rdsMazDbClusterHostListProvider.clusterId, new Topology(expected)); - - final List result = rdsMazDbClusterHostListProvider.getStoredTopology(); - assertEquals(expected, result); - } - - @Test - void testTopologyCache_NoSuggestedClusterId() throws SQLException { - RdsMultiAzDbClusterListProvider.clearAll(); - - RdsMultiAzDbClusterListProvider provider1 = - Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:something://cluster-a.domain.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.domain.com").port(HostSpec.NO_PORT).role(HostRole.WRITER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build()); - - doReturn(topologyClusterA) - .when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsMultiAzDbClusterListProvider provider2 = - Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:something://cluster-b.domain.com/")); - provider2.init(); - assertNull(provider2.getStoredTopology()); - - final List topologyClusterB = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-1.domain.com").port(HostSpec.NO_PORT).role(HostRole.WRITER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-2.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-3.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build()); - doReturn(topologyClusterB).when(provider2).queryForTopology(any(Connection.class)); - - final List topologyProvider2 = provider2.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterB, topologyProvider2); - - assertEquals(2, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_SuggestedClusterIdForRds() throws SQLException { - RdsMultiAzDbClusterListProvider.clearAll(); - - RdsMultiAzDbClusterListProvider provider1 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doReturn(topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsMultiAzDbClusterListProvider provider2 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - final List topologyProvider2 = provider2.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertEquals(1, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_SuggestedClusterIdForInstance() throws SQLException { - RdsMultiAzDbClusterListProvider.clearAll(); - - RdsMultiAzDbClusterListProvider provider1 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doReturn(topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsMultiAzDbClusterListProvider provider2 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://instance-a-3.xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - final List topologyProvider2 = provider2.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertEquals(1, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_AcceptSuggestion() throws SQLException { - RdsMultiAzDbClusterListProvider.clearAll(); - - RdsMultiAzDbClusterListProvider provider1 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://instance-a-2.xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doAnswer(a -> topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - List topologyProvider1 = provider1.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - // RdsMultiAzDbClusterListProvider.logCache(); - - RdsMultiAzDbClusterListProvider provider2 = - Mockito.spy(getRdsMazDbClusterHostListProvider( - "jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - doAnswer(a -> topologyClusterA).when(provider2).queryForTopology(any(Connection.class)); - - final List topologyProvider2 = provider2.refresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertNotEquals(provider1.clusterId, provider2.clusterId); - assertFalse(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - assertEquals(2, storageService.size(Topology.class)); - assertEquals("cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com", - RdsMultiAzDbClusterListProvider.suggestedPrimaryClusterIdCache.get(provider1.clusterId)); - - // RdsMultiAzDbClusterListProvider.logCache(); - - topologyProvider1 = provider1.forceRefresh(Mockito.mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - // RdsMultiAzDbClusterListProvider.logCache(); - } - - @Test - void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - - when(mockResultSet.next()).thenReturn(false); - assertThrows(SQLException.class, () -> rdsMazDbClusterHostListProvider.identifyConnection(mockConnection)); - - when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); - assertThrows(SQLException.class, () -> rdsMazDbClusterHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionNullTopology() throws SQLException { - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - rdsMazDbClusterHostListProvider.clusterInstanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("?.pattern").build(); - - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-1"); - doReturn(null).when(rdsMazDbClusterHostListProvider).refresh(mockConnection); - doReturn(null).when(rdsMazDbClusterHostListProvider).forceRefresh(mockConnection); - - assertNull(rdsMazDbClusterHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionHostNotInTopology() throws SQLException { - final List cachedTopology = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build()); - - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-1"); - doReturn(cachedTopology).when(rdsMazDbClusterHostListProvider).refresh(mockConnection); - doReturn(cachedTopology).when(rdsMazDbClusterHostListProvider).forceRefresh(mockConnection); - - assertNull(rdsMazDbClusterHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionHostInTopology() throws SQLException { - final HostSpec expectedHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .hostId("instance-a-1") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(); - final List cachedTopology = Collections.singletonList(expectedHost); - - rdsMazDbClusterHostListProvider = Mockito.spy(getRdsMazDbClusterHostListProvider("jdbc:someprotocol://url")); - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-a-1"); - doReturn(cachedTopology).when(rdsMazDbClusterHostListProvider).refresh(mockConnection); - doReturn(cachedTopology).when(rdsMazDbClusterHostListProvider).forceRefresh(mockConnection); - - final HostSpec actual = rdsMazDbClusterHostListProvider.identifyConnection(mockConnection); - assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); - assertEquals("instance-a-1", actual.getHostId()); - } - -} diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java index 1fb98265e..d49f76b58 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java @@ -58,7 +58,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; -import software.amazon.jdbc.hostlistprovider.AuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.RdsUrlType; @@ -87,7 +87,7 @@ class FailoverConnectionPluginTest { @Mock Connection mockConnection; @Mock HostSpec mockHostSpec; @Mock HostListProviderService mockHostListProviderService; - @Mock AuroraHostListProvider mockHostListProvider; + @Mock RdsHostListProvider mockHostListProvider; @Mock JdbcCallable mockInitHostProviderFunc; @Mock ReaderFailoverHandler mockReaderFailoverHandler; @Mock WriterFailoverHandler mockWriterFailoverHandler; From 9332a7a94df9a9bf2a65a99a4830e2725c134816 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 09:31:21 -0700 Subject: [PATCH 08/46] Fix messages --- .../jdbc/dialect/AuroraDialectUtils.java | 4 ++-- .../jdbc/dialect/MultiAzDialectUtils.java | 2 +- .../amazon/jdbc/util/TopologyUtils.java | 6 ++--- ..._advanced_jdbc_wrapper_messages.properties | 22 ++++++++----------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java index 647e93cc2..2e4c0fe62 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -44,7 +44,7 @@ public AuroraDialectUtils(String writerIdQuery) { throws SQLException { if (resultSet.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("AuroraTopologyProcessor.unexpectedTopologyQueryColumnCount")); + LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); return null; } @@ -56,7 +56,7 @@ public AuroraDialectUtils(String writerIdQuery) { hosts.add(createHost(resultSet)); } catch (Exception e) { LOGGER.finest( - Messages.get("AuroraTopologyProcessor.errorProcessingQueryResults", new Object[]{e.getMessage()})); + Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); return null; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java index 3a3075429..a598d59a7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -50,7 +50,7 @@ public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, Str hosts.add(host); } catch (Exception e) { LOGGER.finest( - Messages.get("ClusterTopologyMonitorImpl.errorProcessingQueryResults", new Object[]{e.getMessage()})); + Messages.get("MultiAzDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); return null; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java index 59a494322..2d9315926 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -102,7 +102,7 @@ public TopologyUtils( int writerCount = writers.size(); if (writerCount == 0) { - LOGGER.warning(() -> Messages.get("ClusterTopologyMonitorImpl.invalidTopology")); + LOGGER.warning(() -> Messages.get("TopologyUtils.invalidTopology")); return null; } else if (writerCount == 1) { hosts.add(writers.get(0)); @@ -163,9 +163,9 @@ public HostRole getHostRole(Connection conn) throws SQLException { return isReader ? HostRole.READER : HostRole.WRITER; } } catch (SQLException e) { - throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole"), e); + throw new SQLException(Messages.get("TopologyUtils.errorGettingHostRole"), e); } - throw new SQLException(Messages.get("RdsHostListProvider.errorGettingHostRole")); + throw new SQLException(Messages.get("TopologyUtils.errorGettingHostRole")); } } diff --git a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties index e67efb909..3e32cbf0a 100644 --- a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties +++ b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties @@ -27,9 +27,8 @@ AdfsCredentialsProviderFactory.signOnPagePostActionRequestFailed=ADFS SignOn Pag AdfsCredentialsProviderFactory.signOnPageRequestFailed=ADFS SignOn Page Request Failed with HTTP status ''{0}'', reason phrase ''{1}'', and response ''{2}'' AdfsCredentialsProviderFactory.signOnPageUrl=ADFS SignOn URL: ''{0}'' -AuroraTopologyProcessor.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} -AuroraDialect.invalidTopology=The topology query returned an invalid topology - no writer instance detected. -AuroraTopologyProcessor.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. +AuroraDialectUtils.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} +AuroraDialectUtils.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. AuthenticationToken.useCachedToken=Use cached authentication token = ''{0}'' AuthenticationToken.generatedNewToken=Generated new authentication token = ''{0}'' @@ -38,13 +37,9 @@ AuthenticationToken.javaSdkNotInClasspath=Required dependency 'AWS Java SDK RDS RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRDSProxy=An RDS Proxy url can''t be used as the 'clusterInstanceHostPattern' configuration setting. RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRdsCustom=A custom RDS url can''t be used as the 'clusterInstanceHostPattern' configuration setting. RdsHostListProvider.invalidPattern=Invalid value for the 'clusterInstanceHostPattern' configuration setting - the host pattern must contain a '?' character as a placeholder for the DB instance identifiers of the instances in the cluster. -RdsHostListProvider.invalidTopology=The topology query returned an invalid topology - no writer instance detected. RdsHostListProvider.suggestedClusterId=ClusterId ''{0}'' is suggested for url ''{1}''. RdsHostListProvider.parsedListEmpty=Can''t parse connection string: ''{0}'' -RdsHostListProvider.invalidQuery=Error obtaining host list. Provided database might not be an Aurora Db cluster -RdsHostListProvider.errorGettingHostRole=An error occurred while obtaining the connected host's role. This could occur if the connection is broken or if you are not connected to an Aurora database. RdsHostListProvider.errorIdentifyConnection=An error occurred while obtaining the connection's host ID. -RdsHostListProvider.errorGettingNetworkTimeout=An error occurred while getting the connection network timeout: {0} AwsSdk.unsupportedRegion=Unsupported AWS region ''{0}''. For supported regions please read https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html @@ -251,7 +246,6 @@ HostMonitorImpl.interruptedExceptionDuringMonitoring=Monitoring thread for node HostMonitorImpl.exceptionDuringMonitoringContinue=Continuing monitoring after unhandled exception was thrown in monitoring thread for node {0}. HostMonitorImpl.exceptionDuringMonitoringStop=Stopping monitoring after unhandled exception was thrown in monitoring thread for node {0}. HostMonitorImpl.monitorIsStopped=Monitoring was already stopped for node {0}. -HostMonitorImpl.stopped=Stopped monitoring thread for node ''{0}''. HostMonitorImpl.startMonitoringThreadNewContext=Start monitoring thread for checking new contexts for {0}. HostMonitorImpl.stopMonitoringThreadNewContext=Stop monitoring thread for checking new contexts for {0}. HostMonitorImpl.startMonitoringThread=Start monitoring thread for {0}. @@ -272,6 +266,8 @@ MonitorServiceImpl.stopAndRemoveMissingMonitorType=The monitor service received MonitorServiceImpl.stopAndRemoveMonitorsMissingType=The monitor service received a request to stop all monitors with type ''{0}'', but the monitor service does not have any monitors registered under the given type. Please ensure monitors are registered under the correct type. MonitorServiceImpl.unexpectedMonitorClass=Monitor type mismatch - the monitor ''{0}'' was unexpectedly found under the ''{1}'' monitor class category. Please verify that monitors are submitted under their concrete class. +MultiAzDialectUtils.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} + NodeMonitoringThread.detectedWriter=Writer detected by node monitoring thread: ''{0}''. NodeMonitoringThread.invalidWriterQuery=The writer topology query is invalid: {0} NodeMonitoringThread.threadCompleted=Node monitoring thread completed in {0} ms. @@ -360,16 +356,16 @@ TargetDriverDialectManager.unexpectedClass=Unexpected DataSource class. Expected TargetDriverDialect.unsupported=This target driver dialect does not support this operation. MysqlConnectorJDriverHelper.canNotRegister=Can''t register driver com.mysql.cj.jdbc.Driver. +TopologyUtils.errorGettingHostRole=An error occurred while obtaining the connected host's role. This could occur if the connection is broken or if you are not connected to an Aurora database. TopologyUtils.errorGettingNetworkTimeout=An error occurred while getting the connection network timeout: {0} -TopologyUtils.topologyDialectRequired=Unable to fetch topology because the dialect does not support topology queries. Dialect: {0} -TopologyUtils.invalidQuery=An error occurred while attempting to obtain the topology because the topology query was invalid. Please ensure you are connecting to an Aurora or RDS Db cluster. +TopologyUtils.invalidQuery=An error occurred while attempting to obtain the topology because the topology query was invalid. Please ensure you are connecting to an Aurora or RDS cluster. +TopologyUtils.invalidTopology=The topology query returned an invalid topology - no writer instance detected. MariadbDriverHelper.canNotRegister=Can''t register driver org.mariadb.jdbc.Driver. AuroraInitialConnectionStrategyPlugin.unsupportedStrategy=Unsupported host selection strategy ''{0}''. AuroraInitialConnectionStrategyPlugin.requireDynamicProvider=Dynamic host list provider is required. -NodeResponseTimeMonitor.stopped=Stopped Response time thread for node ''{0}''. NodeResponseTimeMonitor.responseTime=Response time for ''{0}'': {1} ms NodeResponseTimeMonitor.interruptedExceptionDuringMonitoring=Response time thread for node {0} was interrupted. NodeResponseTimeMonitor.exceptionDuringMonitoringStop=Stopping thread after unhandled exception was thrown in Response time thread for node {0}. @@ -379,7 +375,7 @@ NodeResponseTimeMonitor.openedConnection=Opened Response time connection: {0}. ClusterTopologyMonitorImpl.startMonitoringThread=[clusterId: ''{0}''] Start cluster topology monitoring thread for ''{1}''. ClusterTopologyMonitorImpl.stopMonitoringThread=Stop cluster topology monitoring thread for ''{0}''. ClusterTopologyMonitorImpl.exceptionDuringMonitoringStop=Stopping cluster topology monitoring after unhandled exception was thrown in monitoring thread for node ''{0}''. -ClusterTopologyMonitorImpl.topologyNotUpdated=Topology hasn''t been updated after {0} ms. +ClusterTopologyMonitorImpl.topologyNotUpdated=Topology has not been updated after {0} ms. ClusterTopologyMonitorImpl.openedMonitoringConnection=Opened monitoring connection to node ''{0}''. ClusterTopologyMonitorImpl.ignoringTopologyRequest=A topology refresh was requested, but the topology was already updated recently. Returning cached hosts: ClusterTopologyMonitorImpl.timeoutSetToZero=A topology refresh was requested, but the given timeout for the request was 0ms. Returning cached hosts: @@ -408,7 +404,7 @@ bgd.interrupted=[{0}] Interrupted. bgd.monitoringUnhandledException=[{0}] Unhandled exception while monitoring blue/green status. bgd.threadCompleted=[{0}] Blue/green status monitoring thread is completed. bgd.statusNotAvailable=[{0}] (status not available) currentPhase: {1} -bgd.usesVersion=[{0}] Blue/Green deployment uses version ''{1}'' which the driver doesn''t support. Version ''{2}'' will be used instead. +bgd.usesVersion=[{0}] Blue/Green deployment uses version ''{1}'' which the driver does not support. Version ''{2}'' will be used instead. bgd.noEntriesInStatusTable=[{0}] No entries in status table. bgd.exception=[{0}] currentPhase: {1}, exception while querying for blue/green status. bgd.unhandledSqlException=[{0}] Unhandled SQLException. From b5b7f5c794f62bcbc1e3a815d11143ce0803b6dd Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 09:40:13 -0700 Subject: [PATCH 09/46] Move HostListProvider/HostListProviderService to hostlistprovider package --- .../jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java | 2 +- .../software/amazon/jdbc/benchmarks/PluginBenchmarks.java | 2 +- .../amazon/jdbc/benchmarks/testplugin/BenchmarkPlugin.java | 2 +- .../jdbc/benchmarks/testplugin/TestConnectionWrapper.java | 2 +- .../java/software/amazon/jdbc/BlockingHostListProvider.java | 1 + .../src/main/java/software/amazon/jdbc/ConnectionPlugin.java | 1 + .../java/software/amazon/jdbc/ConnectionPluginManager.java | 1 + .../main/java/software/amazon/jdbc/PartialPluginService.java | 2 ++ wrapper/src/main/java/software/amazon/jdbc/PluginService.java | 1 + .../src/main/java/software/amazon/jdbc/PluginServiceImpl.java | 2 ++ .../amazon/jdbc/dialect/HostListProviderSupplier.java | 2 +- .../hostlistprovider/ConnectionStringHostListProvider.java | 1 - .../amazon/jdbc/hostlistprovider/DynamicHostListProvider.java | 2 -- .../amazon/jdbc/{ => hostlistprovider}/HostListProvider.java | 4 +++- .../jdbc/{ => hostlistprovider}/HostListProviderService.java | 4 +++- .../amazon/jdbc/hostlistprovider/RdsHostListProvider.java | 1 - .../amazon/jdbc/hostlistprovider/StaticHostListProvider.java | 2 -- .../software/amazon/jdbc/plugin/AbstractConnectionPlugin.java | 2 +- .../jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java | 2 +- .../software/amazon/jdbc/plugin/DefaultConnectionPlugin.java | 3 +-- .../amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java | 2 +- .../amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java | 2 +- .../jdbc/plugin/failover2/FailoverConnectionPlugin.java | 2 +- .../plugin/readwritesplitting/ReadWriteSplittingPlugin.java | 2 +- .../amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java | 2 +- .../amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java | 2 +- .../java/software/amazon/jdbc/util/FullServicesContainer.java | 2 +- .../software/amazon/jdbc/util/FullServicesContainerImpl.java | 2 +- .../main/java/software/amazon/jdbc/util/ServiceUtility.java | 2 +- .../java/software/amazon/jdbc/wrapper/ConnectionWrapper.java | 2 +- .../test/java/software/amazon/jdbc/DialectDetectionTests.java | 1 + .../java/software/amazon/jdbc/PluginServiceImplTests.java | 1 + .../amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java | 1 - .../test/java/software/amazon/jdbc/mock/TestPluginOne.java | 2 +- .../jdbc/plugin/failover/FailoverConnectionPluginTest.java | 2 +- .../jdbc/plugin/limitless/LimitlessConnectionPluginTest.java | 2 +- .../jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java | 2 +- .../readwritesplitting/ReadWriteSplittingPluginTest.java | 2 +- 38 files changed, 39 insertions(+), 33 deletions(-) rename wrapper/src/main/java/software/amazon/jdbc/{ => hostlistprovider}/HostListProvider.java (93%) rename wrapper/src/main/java/software/amazon/jdbc/{ => hostlistprovider}/HostListProviderService.java (89%) diff --git a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java index 413a37e03..4963c09de 100644 --- a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java +++ b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java @@ -53,7 +53,7 @@ import software.amazon.jdbc.ConnectionPluginFactory; import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.JdbcMethod; diff --git a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/PluginBenchmarks.java b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/PluginBenchmarks.java index a9c10b2f6..4d5ae319a 100644 --- a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/PluginBenchmarks.java +++ b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/PluginBenchmarks.java @@ -53,7 +53,7 @@ import software.amazon.jdbc.ConnectionProviderManager; import software.amazon.jdbc.Driver; import software.amazon.jdbc.HikariPooledConnectionProvider; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.JdbcMethod; diff --git a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/BenchmarkPlugin.java b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/BenchmarkPlugin.java index ffed10f77..07fede27a 100644 --- a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/BenchmarkPlugin.java +++ b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/BenchmarkPlugin.java @@ -28,7 +28,7 @@ import java.util.Set; import java.util.logging.Logger; import software.amazon.jdbc.ConnectionPlugin; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/TestConnectionWrapper.java b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/TestConnectionWrapper.java index a3c0cd7f2..483d6768c 100644 --- a/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/TestConnectionWrapper.java +++ b/benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/testplugin/TestConnectionWrapper.java @@ -20,7 +20,7 @@ import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.ConnectionPluginManager; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.wrapper.ConnectionWrapper; diff --git a/wrapper/src/main/java/software/amazon/jdbc/BlockingHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/BlockingHostListProvider.java index 9fe7e40fb..31f3d1182 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/BlockingHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/BlockingHostListProvider.java @@ -19,6 +19,7 @@ import java.sql.SQLException; import java.util.List; import java.util.concurrent.TimeoutException; +import software.amazon.jdbc.hostlistprovider.HostListProvider; public interface BlockingHostListProvider extends HostListProvider { diff --git a/wrapper/src/main/java/software/amazon/jdbc/ConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/ConnectionPlugin.java index d2d72b05c..ad271f2ad 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/ConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/ConnectionPlugin.java @@ -23,6 +23,7 @@ import java.util.Map; import java.util.Properties; import java.util.Set; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; /** * Interface for connection plugins. This class implements ways to execute a JDBC method and to clean up resources used diff --git a/wrapper/src/main/java/software/amazon/jdbc/ConnectionPluginManager.java b/wrapper/src/main/java/software/amazon/jdbc/ConnectionPluginManager.java index 2697c5b03..33f7618b9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/ConnectionPluginManager.java +++ b/wrapper/src/main/java/software/amazon/jdbc/ConnectionPluginManager.java @@ -30,6 +30,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.cleanup.CanReleaseResources; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AuroraConnectionTrackerPlugin; import software.amazon.jdbc.plugin.AuroraInitialConnectionStrategyPlugin; import software.amazon.jdbc.plugin.AwsSecretsManagerConnectionPlugin; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 51ee4d6fd..189c03c62 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -41,6 +41,8 @@ import software.amazon.jdbc.exceptions.ExceptionManager; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.HostAvailabilityStrategyFactory; +import software.amazon.jdbc.hostlistprovider.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.hostlistprovider.StaticHostListProvider; import software.amazon.jdbc.profile.ConfigurationProfile; import software.amazon.jdbc.states.SessionStateService; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PluginService.java index b01679aba..e3cbada1d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginService.java @@ -28,6 +28,7 @@ import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.states.SessionStateService; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.telemetry.TelemetryFactory; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index dd8aa420b..2c34797a2 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -46,6 +46,8 @@ import software.amazon.jdbc.exceptions.ExceptionManager; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.HostAvailabilityStrategyFactory; +import software.amazon.jdbc.hostlistprovider.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.hostlistprovider.StaticHostListProvider; import software.amazon.jdbc.profile.ConfigurationProfile; import software.amazon.jdbc.states.SessionStateService; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/HostListProviderSupplier.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/HostListProviderSupplier.java index 0dfe44dc5..bee378f9f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/HostListProviderSupplier.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/HostListProviderSupplier.java @@ -18,7 +18,7 @@ import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; -import software.amazon.jdbc.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.util.FullServicesContainer; @FunctionalInterface diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java index 80f55bdad..bbf3209ed 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java @@ -25,7 +25,6 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.util.ConnectionUrlParser; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java index 451c047f3..c4ef01aae 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java @@ -16,8 +16,6 @@ package software.amazon.jdbc.hostlistprovider; -import software.amazon.jdbc.HostListProvider; - // A marker interface for providers that can fetch a host list, and it changes depending on database status // A good example of such provider would be DB cluster provider (Aurora DB clusters, patroni DB clusters, etc.) // where cluster topology (nodes, their roles, their statuses) changes over time. diff --git a/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProvider.java similarity index 93% rename from wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProvider.java index 1a147658f..206a35415 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/HostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProvider.java @@ -14,12 +14,14 @@ * limitations under the License. */ -package software.amazon.jdbc; +package software.amazon.jdbc.hostlistprovider; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; public interface HostListProvider { diff --git a/wrapper/src/main/java/software/amazon/jdbc/HostListProviderService.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProviderService.java similarity index 89% rename from wrapper/src/main/java/software/amazon/jdbc/HostListProviderService.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProviderService.java index b2f6b5353..0413cb423 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/HostListProviderService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/HostListProviderService.java @@ -14,9 +14,11 @@ * limitations under the License. */ -package software.amazon.jdbc; +package software.amazon.jdbc.hostlistprovider; import java.sql.Connection; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.Dialect; public interface HostListProviderService { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 65054208b..262f65f23 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -33,7 +33,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java index b37eb4cc3..8229e2cd3 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java @@ -16,8 +16,6 @@ package software.amazon.jdbc.hostlistprovider; -import software.amazon.jdbc.HostListProvider; - // A marker interface for providers that fetch node lists, and it never changes since after. // An example of such provider is a provider that use connection string as a source. public interface StaticHostListProvider extends HostListProvider {} diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java index 035e4ecf9..22c5b13aa 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java @@ -24,7 +24,7 @@ import java.util.Properties; import java.util.Set; import software.amazon.jdbc.ConnectionPlugin; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index 568f12cb6..42cefc684 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -27,7 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java index b879ba60b..b69c1cdad 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java @@ -16,7 +16,6 @@ package software.amazon.jdbc.plugin; -import java.beans.beancontext.BeanContext; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; @@ -35,7 +34,7 @@ import software.amazon.jdbc.ConnectionPlugin; import software.amazon.jdbc.ConnectionProvider; import software.amazon.jdbc.ConnectionProviderManager; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java index 057b152a6..637791549 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java @@ -43,7 +43,7 @@ import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PluginService; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 85dd4a52a..a0dfdb7e9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -33,7 +33,7 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 19b8710a4..daf670f79 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -31,7 +31,7 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index 2d25675e2..27263c19c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -28,7 +28,7 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 6ad27b8a2..8aacc6528 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -25,7 +25,7 @@ import java.util.Map; import java.util.Properties; import java.util.logging.Logger; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java index a5babfc3c..b3cea1d1d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java @@ -25,7 +25,7 @@ import java.util.Properties; import java.util.Set; import java.util.logging.Logger; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; import software.amazon.jdbc.JdbcMethod; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java index 7b7857175..cbe122ed8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java @@ -18,7 +18,7 @@ import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.util.monitoring.MonitorService; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java index db0ea3f57..349239668 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java @@ -18,7 +18,7 @@ import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.util.monitoring.MonitorService; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java b/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java index fe150f835..e06261bcb 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java @@ -21,7 +21,7 @@ import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.PartialPluginService; import software.amazon.jdbc.PluginServiceImpl; import software.amazon.jdbc.dialect.Dialect; diff --git a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java index c29f11c2a..a57d23183 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java @@ -39,7 +39,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.ConnectionPluginManager; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.JdbcMethod; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; diff --git a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java index 3b47f12bb..d86238214 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java @@ -50,6 +50,7 @@ import software.amazon.jdbc.dialect.RdsMysqlDialect; import software.amazon.jdbc.dialect.RdsPgDialect; import software.amazon.jdbc.exceptions.ExceptionManager; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.storage.StorageService; diff --git a/wrapper/src/test/java/software/amazon/jdbc/PluginServiceImplTests.java b/wrapper/src/test/java/software/amazon/jdbc/PluginServiceImplTests.java index db9a072a1..5ef1235ad 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/PluginServiceImplTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/PluginServiceImplTests.java @@ -62,6 +62,7 @@ import software.amazon.jdbc.exceptions.ExceptionManager; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.profile.ConfigurationProfile; import software.amazon.jdbc.profile.ConfigurationProfileBuilder; import software.amazon.jdbc.states.SessionStateService; diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 3574af62a..e5e00aeaf 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -55,7 +55,6 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java b/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java index 9ca4c86dd..36f077f73 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java +++ b/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java @@ -27,7 +27,7 @@ import java.util.Properties; import java.util.Set; import software.amazon.jdbc.ConnectionPlugin; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java index d49f76b58..859472bb4 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java @@ -49,7 +49,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java index 411233100..13de74a6b 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java @@ -35,7 +35,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import software.amazon.jdbc.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java index 7c12f83fb..0b3e7c2a5 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java @@ -39,7 +39,7 @@ import org.mockito.MockitoAnnotations; import software.amazon.jdbc.ConnectionProvider; import software.amazon.jdbc.HighestWeightHostSelector; -import software.amazon.jdbc.HostListProvider; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java index c7c7bdc1b..834e932d2 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java @@ -45,7 +45,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.HostListProviderService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; From 15c9a47deae3bf27191b4dd261b6e3c1be695dd0 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 10:08:35 -0700 Subject: [PATCH 10/46] node -> instance, suggestedWriter -> writer --- .../amazon/jdbc/dialect/AuroraDialectUtils.java | 9 +++------ .../amazon/jdbc/dialect/AuroraMysqlDialect.java | 10 ++++------ .../amazon/jdbc/dialect/AuroraPgDialect.java | 13 ++++++------- .../jdbc/dialect/MultiAzDialectUtils.java | 12 ++++++------ .../RdsMultiAzDbClusterMysqlDialect.java | 16 ++++++++-------- .../dialect/RdsMultiAzDbClusterPgDialect.java | 17 +++++++++-------- .../amazon/jdbc/dialect/TopologyDialect.java | 3 +-- 7 files changed, 37 insertions(+), 43 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java index 2e4c0fe62..6d168153a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -26,15 +26,12 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; -import org.jsoup.internal.StringUtil; -import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; -import software.amazon.jdbc.util.Utils; public class AuroraDialectUtils { private static final Logger LOGGER = Logger.getLogger(AuroraDialectUtils.class.getName()); - protected String writerIdQuery; + protected final String writerIdQuery; public AuroraDialectUtils(String writerIdQuery) { this.writerIdQuery = writerIdQuery; @@ -66,7 +63,7 @@ public AuroraDialectUtils(String writerIdQuery) { protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { // According to the topology query the result set should contain 4 columns: - // node ID, 1/0 (writer/reader), CPU utilization, instance lag + // instance ID, 1/0 (writer/reader), CPU utilization, instance lag String hostName = resultSet.getString(1); final boolean isWriter = resultSet.getBoolean(2); final float cpuUtilization = resultSet.getFloat(3); @@ -78,7 +75,7 @@ protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQL lastUpdateTime = Timestamp.from(Instant.now()); } - // Calculate weight based on node lag in time and CPU utilization. + // Calculate weight based on instance lag in time and CPU utilization. final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); return new TopologyQueryHostSpec(hostName, isWriter, weight, lastUpdateTime); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index c4c22b2eb..ce52f6532 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -23,8 +23,6 @@ import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, BlueGreenDialect { @@ -33,14 +31,14 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "CPU, REPLICA_LAG_IN_MILLISECONDS, LAST_UPDATE_TIMESTAMP " + "FROM information_schema.replica_host_status " - // filter out nodes that haven't been updated in the last 5 minutes + // filter out instances that haven't been updated in the last 5 minutes + "WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID' "; private static final String IS_WRITER_QUERY = "SELECT SERVER_ID FROM information_schema.replica_host_status " + "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id"; - private static final String NODE_ID_QUERY = "SELECT @@aurora_server_id"; + private static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; private static final String IS_READER_QUERY = "SELECT @@innodb_read_only"; private static final String BG_STATUS_QUERY = @@ -118,7 +116,7 @@ public String getTopologyQuery() { } @Override - public @Nullable List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + public @Nullable List processQueryResults(ResultSet rs, @Nullable String writerId) throws SQLException { return AuroraMysqlDialect.dialectUtils.processQueryResults(rs); } @@ -141,7 +139,7 @@ public String getIsReaderQuery() { @Override public String getInstanceIdQuery() { - return NODE_ID_QUERY; + return INSTANCE_ID_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 191ac849f..2d74d2db4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.util.DriverInfo; @@ -45,18 +44,18 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror "SELECT SERVER_ID, CASE WHEN SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "CPU, COALESCE(REPLICA_LAG_IN_MSEC, 0), LAST_UPDATE_TIMESTAMP " + "FROM pg_catalog.aurora_replica_status() " - // filter out nodes that haven't been updated in the last 5 minutes + // filter out instances that haven't been updated in the last 5 minutes + "WHERE EXTRACT(" + "EPOCH FROM(pg_catalog.NOW() OPERATOR(pg_catalog.-) LAST_UPDATE_TIMESTAMP)) OPERATOR(pg_catalog.<=) 300 " + "OR SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " + "OR LAST_UPDATE_TIMESTAMP IS NULL"; - private static final String IS_WRITER_QUERY = + private static final String WRITER_ID_QUERY = "SELECT SERVER_ID FROM pg_catalog.aurora_replica_status() " + "WHERE SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " + "AND SERVER_ID OPERATOR(pg_catalog.=) pg_catalog.aurora_db_instance_identifier()"; - private static final String NODE_ID_QUERY = "SELECT pg_catalog.aurora_db_instance_identifier()"; + private static final String INSTANCE_ID_QUERY = "SELECT pg_catalog.aurora_db_instance_identifier()"; private static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; protected static final String LIMITLESS_ROUTER_ENDPOINT_QUERY = "select router_endpoint, load from pg_catalog.aurora_limitless_router_endpoints()"; @@ -68,7 +67,7 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror private static final String TOPOLOGY_TABLE_EXIST_QUERY = "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc"; - private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); + private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); @Override public boolean isDialect(final Connection connection) { @@ -158,7 +157,7 @@ public String getIsReaderQuery() { @Override public String getInstanceIdQuery() { - return NODE_ID_QUERY; + return INSTANCE_ID_QUERY; } @Override @@ -173,7 +172,7 @@ public boolean isWriterInstance(Connection connection) throws SQLException { } @Override - public @Nullable List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + public @Nullable List processQueryResults(ResultSet rs, @Nullable String writerId) throws SQLException { return AuroraPgDialect.dialectUtils.processQueryResults(rs); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java index a598d59a7..7c402bc78 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -41,12 +41,12 @@ public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, Str this.instanceIdQuery = instanceIdQuery; } - public @Nullable List processQueryResults(ResultSet resultSet, String suggestedWriterId) + public @Nullable List processQueryResults(ResultSet resultSet, String writerId) throws SQLException { List hosts = new ArrayList<>(); while (resultSet.next()) { try { - final TopologyQueryHostSpec host = createHost(resultSet, suggestedWriterId); + final TopologyQueryHostSpec host = createHost(resultSet, writerId); hosts.add(host); } catch (Exception e) { LOGGER.finest( @@ -60,12 +60,12 @@ public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, Str protected TopologyQueryHostSpec createHost( final ResultSet resultSet, - final String suggestedWriterNodeId) throws SQLException { + final String writerId) throws SQLException { String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" String hostId = resultSet.getString("id"); // "1034958454" - final boolean isWriter = hostId.equals(suggestedWriterNodeId); + final boolean isWriter = hostId.equals(writerId); return new TopologyQueryHostSpec(instanceName, isWriter, 0, Timestamp.from(Instant.now())); } @@ -96,10 +96,10 @@ public boolean isWriterInstance(final Connection connection) throws SQLException try (final Statement stmt = connection.createStatement()) { try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { if (resultSet.next()) { - String nodeId = resultSet.getString(this.writerIdQueryColumn); + String instanceId = resultSet.getString(this.writerIdQueryColumn); // The writer ID is only returned when connected to a reader, so if the query does not return a value, it // means we are connected to a writer. - return StringUtils.isNullOrEmpty(nodeId); + return StringUtils.isNullOrEmpty(instanceId); } } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index da55b460a..90e99c478 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -43,12 +43,12 @@ public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect implements Top "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; - // For reader nodes, the query returns a writer node ID. For a writer node, the query returns no data. - private static final String FETCH_WRITER_NODE_QUERY = "SHOW REPLICA STATUS"; + // For reader instances, the query returns a writer instance ID. For a writer instance, the query returns no data. + private static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; - private static final String FETCH_WRITER_NODE_QUERY_COLUMN_NAME = "Source_Server_Id"; + private static final String WRITER_ID_QUERY_COLUMN = "Source_Server_Id"; - private static final String NODE_ID_QUERY = "SELECT @@server_id"; + private static final String INSTANCE_ID_QUERY = "SELECT @@server_id"; private static final String IS_READER_QUERY = "SELECT @@read_only"; private static final EnumSet RDS_MULTI_AZ_RESTRICTIONS = @@ -56,7 +56,7 @@ public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect implements Top protected final RdsUtils rdsUtils = new RdsUtils(); protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - FETCH_WRITER_NODE_QUERY, FETCH_WRITER_NODE_QUERY_COLUMN_NAME, NODE_ID_QUERY); + WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN, INSTANCE_ID_QUERY); @Override public boolean isDialect(final Connection connection) { @@ -130,9 +130,9 @@ public String getTopologyQuery() { } @Override - public List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) + public List processQueryResults(ResultSet rs, @Nullable String writerId) throws SQLException { - return dialectUtils.processQueryResults(rs, suggestedWriterId); + return dialectUtils.processQueryResults(rs, writerId); } @Override @@ -152,6 +152,6 @@ public String getIsReaderQuery() { @Override public String getInstanceIdQuery() { - return NODE_ID_QUERY; + return INSTANCE_ID_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index 14041d849..772e836a8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -40,8 +40,9 @@ public class RdsMultiAzDbClusterPgDialect extends PgDialect implements TopologyD private static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - // For reader nodes, the query should return a writer node ID. For a writer node, the query should return no data. - private static final String FETCH_WRITER_NODE_QUERY = + // For reader instances, the query should return a writer instance ID. + // For a writer instance, the query should return no data. + private static final String WRITER_ID_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()" + " WHERE multi_az_db_cluster_source_dbi_resource_id OPERATOR(pg_catalog.!=)" + " (SELECT dbi_resource_id FROM rds_tools.dbi_resource_id())"; @@ -49,14 +50,14 @@ public class RdsMultiAzDbClusterPgDialect extends PgDialect implements TopologyD private static final String IS_RDS_CLUSTER_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"; - private static final String FETCH_WRITER_NODE_QUERY_COLUMN_NAME = "multi_az_db_cluster_source_dbi_resource_id"; + private static final String WRITER_ID_QUERY_COLUMN = "multi_az_db_cluster_source_dbi_resource_id"; - private static final String NODE_ID_QUERY = "SELECT dbi_resource_id FROM rds_tools.dbi_resource_id()"; + private static final String INSTANCE_ID_QUERY = "SELECT dbi_resource_id FROM rds_tools.dbi_resource_id()"; private static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - FETCH_WRITER_NODE_QUERY, FETCH_WRITER_NODE_QUERY_COLUMN_NAME, NODE_ID_QUERY); + WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN, INSTANCE_ID_QUERY); @Override public ExceptionHandler getExceptionHandler() { @@ -116,12 +117,12 @@ public String getIsReaderQuery() { @Override public String getInstanceIdQuery() { - return NODE_ID_QUERY; + return INSTANCE_ID_QUERY; } @Override - public @Nullable List processQueryResults(ResultSet rs, String suggestedWriterId) + public @Nullable List processQueryResults(ResultSet rs, String writerId) throws SQLException { - return this.dialectUtils.processQueryResults(rs, suggestedWriterId); + return this.dialectUtils.processQueryResults(rs, writerId); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index 928309741..57422b559 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -26,8 +26,7 @@ public interface TopologyDialect extends Dialect { String getTopologyQuery(); @Nullable - List processQueryResults(ResultSet rs, @Nullable String suggestedWriterId) - throws SQLException; + List processQueryResults(ResultSet rs, @Nullable String writerId) throws SQLException; @Nullable String getWriterId(final Connection connection) throws SQLException; From d021e00564adb3aa909f1a65130aa00370d0b506 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 10:22:02 -0700 Subject: [PATCH 11/46] Remove getWriterId from TopologyDialect --- .../software/amazon/jdbc/dialect/AuroraMysqlDialect.java | 9 +-------- .../software/amazon/jdbc/dialect/AuroraPgDialect.java | 8 +------- .../amazon/jdbc/dialect/MultiAzDialectUtils.java | 8 ++++---- .../jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java | 9 ++------- .../jdbc/dialect/RdsMultiAzDbClusterPgDialect.java | 9 ++------- .../software/amazon/jdbc/dialect/TopologyDialect.java | 5 +---- .../java/software/amazon/jdbc/util/TopologyUtils.java | 3 +-- 7 files changed, 12 insertions(+), 39 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index ce52f6532..a890b4f29 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -116,17 +116,10 @@ public String getTopologyQuery() { } @Override - public @Nullable List processQueryResults(ResultSet rs, @Nullable String writerId) - throws SQLException { + public @Nullable List processQueryResults(Connection conn, ResultSet rs) throws SQLException { return AuroraMysqlDialect.dialectUtils.processQueryResults(rs); } - @Override - @Nullable public String getWriterId(final Connection connection) { - // The Aurora topology query can detect the writer without a suggested writer ID, so we intentionally return null. - return null; - } - @Override public boolean isWriterInstance(Connection connection) throws SQLException { return dialectUtils.isWriterInstance(connection); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 2d74d2db4..c93d306d9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -160,19 +160,13 @@ public String getInstanceIdQuery() { return INSTANCE_ID_QUERY; } - @Override - @Nullable public String getWriterId(final Connection connection) { - // The Aurora topology query can detect the writer without a suggested writer ID, so we intentionally return null. - return null; - } - @Override public boolean isWriterInstance(Connection connection) throws SQLException { return AuroraPgDialect.dialectUtils.isWriterInstance(connection); } @Override - public @Nullable List processQueryResults(ResultSet rs, @Nullable String writerId) + public @Nullable List processQueryResults(Connection conn, ResultSet rs) throws SQLException { return AuroraPgDialect.dialectUtils.processQueryResults(rs); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java index 7c402bc78..5cdcb5e66 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -41,12 +41,12 @@ public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, Str this.instanceIdQuery = instanceIdQuery; } - public @Nullable List processQueryResults(ResultSet resultSet, String writerId) + public @Nullable List processQueryResults(Connection conn, ResultSet resultSet) throws SQLException { List hosts = new ArrayList<>(); while (resultSet.next()) { try { - final TopologyQueryHostSpec host = createHost(resultSet, writerId); + final TopologyQueryHostSpec host = createHost(resultSet, this.getWriterId(conn)); hosts.add(host); } catch (Exception e) { LOGGER.finest( @@ -60,7 +60,7 @@ public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, Str protected TopologyQueryHostSpec createHost( final ResultSet resultSet, - final String writerId) throws SQLException { + final @Nullable String writerId) throws SQLException { String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" @@ -70,7 +70,7 @@ protected TopologyQueryHostSpec createHost( return new TopologyQueryHostSpec(instanceName, isWriter, 0, Timestamp.from(Instant.now())); } - public @Nullable String getWriterId(Connection connection) throws SQLException { + protected @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { if (resultSet.next()) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index 90e99c478..c318b6b3d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -130,14 +130,9 @@ public String getTopologyQuery() { } @Override - public List processQueryResults(ResultSet rs, @Nullable String writerId) + public List processQueryResults(Connection conn, ResultSet rs) throws SQLException { - return dialectUtils.processQueryResults(rs, writerId); - } - - @Override - public @Nullable String getWriterId(Connection connection) throws SQLException { - return dialectUtils.getWriterId(connection); + return dialectUtils.processQueryResults(conn, rs); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index 772e836a8..fe60cb2e1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -100,11 +100,6 @@ public String getTopologyQuery() { return TOPOLOGY_QUERY; } - @Override - public @Nullable String getWriterId(final Connection connection) throws SQLException { - return dialectUtils.getWriterId(connection); - } - @Override public boolean isWriterInstance(Connection connection) throws SQLException { return dialectUtils.isWriterInstance(connection); @@ -121,8 +116,8 @@ public String getInstanceIdQuery() { } @Override - public @Nullable List processQueryResults(ResultSet rs, String writerId) + public @Nullable List processQueryResults(Connection conn, ResultSet rs) throws SQLException { - return this.dialectUtils.processQueryResults(rs, writerId); + return this.dialectUtils.processQueryResults(conn, rs); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index 57422b559..f7cd8a694 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -26,10 +26,7 @@ public interface TopologyDialect extends Dialect { String getTopologyQuery(); @Nullable - List processQueryResults(ResultSet rs, @Nullable String writerId) throws SQLException; - - @Nullable - String getWriterId(final Connection connection) throws SQLException; + List processQueryResults(Connection conn, ResultSet rs) throws SQLException; // TODO: can we remove this and use getHostRole instead? boolean isWriterInstance(final Connection connection) throws SQLException; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java index 2d9315926..598dc73bb 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -71,10 +71,9 @@ public TopologyUtils( new Object[] {e.getMessage()})); } - final String writerId = this.dialect.getWriterId(conn); try (final Statement stmt = conn.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - List queryHosts = this.dialect.processQueryResults(resultSet, writerId); + List queryHosts = this.dialect.processQueryResults(conn, resultSet); return this.processQueryResults(queryHosts); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); From 2cb200aa579c7cede77d9e4f688e5325abecf0eb Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 17:03:53 -0700 Subject: [PATCH 12/46] Revert "wip" This reverts commit 9ce95e532e217b2bdd6fcb00ca7fc6eb37f445e0. --- .../main/java/software/amazon/jdbc/PluginServiceImpl.java | 2 +- .../plugin/AuroraInitialConnectionStrategyPlugin.java | 8 ++++---- .../failover/ClusterAwareWriterFailoverHandler.java | 4 ++-- .../jdbc/plugin/failover/FailoverConnectionPlugin.java | 2 +- .../jdbc/plugin/failover2/FailoverConnectionPlugin.java | 2 +- .../amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 2c34797a2..813d5dade 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -732,7 +732,7 @@ public void updateDialect(final @NonNull Connection connection) throws SQLExcept final HostListProviderSupplier supplier = this.dialect.getHostListProvider(); this.setHostListProvider(supplier.getProvider(this.props, this.originalUrl, this.servicesContainer)); // TODO: refreshHostList - this.refreshHostList(); + this.refreshHostList(connection); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index 42cefc684..22672144a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -195,7 +195,7 @@ private Connection getVerifiedWriterConnection( // Writer is not found. It seems that topology is outdated. writerCandidateConn = connectFunc.call(); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(writerCandidateConn); writerCandidate = this.pluginService.identifyConnection(writerCandidateConn); if (writerCandidate == null || writerCandidate.getRole() != HostRole.WRITER) { @@ -217,7 +217,7 @@ private Connection getVerifiedWriterConnection( // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(writerCandidateConn); this.closeConnection(writerCandidateConn); this.delay(retryDelayMs); continue; @@ -274,7 +274,7 @@ private Connection getVerifiedReaderConnection( // Reader is not found. It seems that topology is outdated. readerCandidateConn = connectFunc.call(); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(readerCandidateConn); readerCandidate = this.pluginService.identifyConnection(readerCandidateConn); if (readerCandidate == null) { @@ -309,7 +309,7 @@ private Connection getVerifiedReaderConnection( // If the new connection resolves to a writer instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(readerCandidateConn); if (this.hasNoReaders()) { // It seems that cluster has no readers. Simulate Aurora reader cluster endpoint logic diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 49f92b78b..9ffd4ef65 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -284,7 +284,7 @@ public WriterFailoverResult call() { conn = this.pluginService.forceConnect(this.originalWriterHost, this.props); // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(conn); latestTopology = this.pluginService.getAllHosts(); } catch (final SQLException exception) { // Propagate exceptions that are not caused by network errors. @@ -446,7 +446,7 @@ private boolean refreshTopologyAndConnectToNewWriter() throws InterruptedExcepti while (true) { try { // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(this.currentReaderConnection); final List topology = this.pluginService.getAllHosts(); if (!topology.isEmpty()) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index a0dfdb7e9..86b92f8e1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -937,7 +937,7 @@ public Connection connect( if (isInitialConnection) { // TODO: refreshHostList - this.pluginService.refreshHostList(); + this.pluginService.refreshHostList(conn); } return conn; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index daf670f79..f1ec20f83 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -788,7 +788,7 @@ public Connection connect( if (isInitialConnection) { // TODO: refreshHostList - this.pluginService.refreshHostList(); + this.pluginService.refreshHostList(conn); } return conn; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 8aacc6528..985df22b5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -92,10 +92,10 @@ public Connection getVerifiedConnection( // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. // TODO: forceRefreshHostList - this.pluginService.forceRefreshHostList(); + this.pluginService.forceRefreshHostList(conn); } else { // TODO: refreshHostList - this.pluginService.refreshHostList(); + this.pluginService.refreshHostList(conn); } LOGGER.finest(() -> Utils.logTopology(this.pluginService.getAllHosts())); From 286e98cdbdbe36cb51f0374af83ea203ef0ee093 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 29 Oct 2025 17:09:16 -0700 Subject: [PATCH 13/46] Revert "wip" This reverts commit 9fdae84c7dfec6f9ef62450807b7f3d1a9bb4f50. --- .../java/software/amazon/jdbc/PluginServiceImpl.java | 1 - .../amazon/jdbc/dialect/AuroraMysqlDialect.java | 12 ++++++++++-- .../amazon/jdbc/dialect/AuroraPgDialect.java | 12 ++++++++++-- .../AuroraInitialConnectionStrategyPlugin.java | 4 ---- .../failover/ClusterAwareWriterFailoverHandler.java | 2 -- .../plugin/failover/FailoverConnectionPlugin.java | 1 - .../plugin/failover2/FailoverConnectionPlugin.java | 1 - .../jdbc/plugin/staledns/AuroraStaleDnsHelper.java | 2 -- 8 files changed, 20 insertions(+), 15 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 813d5dade..69e4b6664 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -731,7 +731,6 @@ public void updateDialect(final @NonNull Connection connection) throws SQLExcept final HostListProviderSupplier supplier = this.dialect.getHostListProvider(); this.setHostListProvider(supplier.getProvider(this.props, this.originalUrl, this.servicesContainer)); - // TODO: refreshHostList this.refreshHostList(connection); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index a890b4f29..7aa9e1d7c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -23,7 +23,10 @@ import java.util.Collections; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; +import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, BlueGreenDialect { @@ -89,8 +92,13 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> - new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + return (properties, initialUrl, servicesContainer) -> { + final PluginService pluginService = servicesContainer.getPluginService(); + if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { + return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + } + return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); + }; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index c93d306d9..26dd11dc4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -23,7 +23,10 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; +import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; /** @@ -141,8 +144,13 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> - new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + return (properties, initialUrl, servicesContainer) -> { + final PluginService pluginService = servicesContainer.getPluginService(); + if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { + return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + } + return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); + }; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index 22672144a..a828183e1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -194,7 +194,6 @@ private Connection getVerifiedWriterConnection( // Writer is not found. It seems that topology is outdated. writerCandidateConn = connectFunc.call(); - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(writerCandidateConn); writerCandidate = this.pluginService.identifyConnection(writerCandidateConn); @@ -216,7 +215,6 @@ private Connection getVerifiedWriterConnection( if (this.pluginService.getHostRole(writerCandidateConn) != HostRole.WRITER) { // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(writerCandidateConn); this.closeConnection(writerCandidateConn); this.delay(retryDelayMs); @@ -273,7 +271,6 @@ private Connection getVerifiedReaderConnection( // Reader is not found. It seems that topology is outdated. readerCandidateConn = connectFunc.call(); - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(readerCandidateConn); readerCandidate = this.pluginService.identifyConnection(readerCandidateConn); @@ -308,7 +305,6 @@ private Connection getVerifiedReaderConnection( if (this.pluginService.getHostRole(readerCandidateConn) != HostRole.READER) { // If the new connection resolves to a writer instance, this means the topology is outdated. // Force refresh to update the topology. - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(readerCandidateConn); if (this.hasNoReaders()) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 9ffd4ef65..8504842c6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -283,7 +283,6 @@ public WriterFailoverResult call() { } conn = this.pluginService.forceConnect(this.originalWriterHost, this.props); - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(conn); latestTopology = this.pluginService.getAllHosts(); } catch (final SQLException exception) { @@ -445,7 +444,6 @@ private boolean refreshTopologyAndConnectToNewWriter() throws InterruptedExcepti while (true) { try { - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(this.currentReaderConnection); final List topology = this.pluginService.getAllHosts(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 86b92f8e1..30fcb0701 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -936,7 +936,6 @@ public Connection connect( } if (isInitialConnection) { - // TODO: refreshHostList this.pluginService.refreshHostList(conn); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index f1ec20f83..c0b1a7660 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -787,7 +787,6 @@ public Connection connect( } if (isInitialConnection) { - // TODO: refreshHostList this.pluginService.refreshHostList(conn); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 985df22b5..6050df3ef 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -91,10 +91,8 @@ public Connection getVerifiedConnection( // This is if-statement is only reached if the connection url is a writer cluster endpoint. // If the new connection resolves to a reader instance, this means the topology is outdated. // Force refresh to update the topology. - // TODO: forceRefreshHostList this.pluginService.forceRefreshHostList(conn); } else { - // TODO: refreshHostList this.pluginService.refreshHostList(conn); } From 6ae0b390f72746dbd6b792f1683ceb5e83975458 Mon Sep 17 00:00:00 2001 From: Qu Chen Date: Wed, 29 Oct 2025 17:29:02 -0700 Subject: [PATCH 14/46] docs: Update integration testing guide to include a hint to do a clean build when experiencing issues (#1584) --- docs/development-guide/IntegrationTests.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/development-guide/IntegrationTests.md b/docs/development-guide/IntegrationTests.md index 62cf5168f..2dec6b0b1 100644 --- a/docs/development-guide/IntegrationTests.md +++ b/docs/development-guide/IntegrationTests.md @@ -69,3 +69,15 @@ cmd /c ./gradlew --no-parallel --no-daemon test-all-environments ``` Test results can be found at `wrapper/build/report/index.html`. + +If you encounter unexplained build issues/errors, or after major project structure changes, try running the following to perform a clean build: + +macOS: +```bash +./gradlew clean +``` + +Windows: +```bash +cmd /c ./gradlew clean +``` From c1ef2c016a67e5a97bf3343ad8fed126b4b5aed7 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 30 Oct 2025 17:02:02 -0700 Subject: [PATCH 15/46] Detailed cleanup --- .../jdbc/dialect/AuroraDialectUtils.java | 6 +- .../jdbc/dialect/AuroraMysqlDialect.java | 87 ++++++-------- .../amazon/jdbc/dialect/AuroraPgDialect.java | 107 ++++++------------ .../amazon/jdbc/dialect/BlueGreenDialect.java | 4 +- .../software/amazon/jdbc/dialect/Dialect.java | 15 +-- .../amazon/jdbc/dialect/MariaDbDialect.java | 83 ++++++-------- .../jdbc/dialect/MultiAzDialectUtils.java | 24 +++- .../amazon/jdbc/dialect/MysqlDialect.java | 81 ++++++------- .../amazon/jdbc/dialect/PgDialect.java | 85 ++++++-------- .../RdsMultiAzDbClusterMysqlDialect.java | 57 ++++------ .../dialect/RdsMultiAzDbClusterPgDialect.java | 74 +++++------- .../amazon/jdbc/dialect/RdsMysqlDialect.java | 75 +++++------- .../amazon/jdbc/dialect/RdsPgDialect.java | 55 +++------ .../amazon/jdbc/dialect/TopologyDialect.java | 9 +- .../hostlistprovider/RdsHostListProvider.java | 16 +-- .../MonitoringRdsHostListProvider.java | 7 +- .../amazon/jdbc/util/TopologyUtils.java | 8 +- 17 files changed, 316 insertions(+), 477 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java index 6d168153a..14d802eed 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -30,14 +30,16 @@ import software.amazon.jdbc.util.StringUtils; public class AuroraDialectUtils { - private static final Logger LOGGER = Logger.getLogger(AuroraDialectUtils.class.getName()); + protected final String writerIdQuery; + private static final Logger LOGGER = Logger.getLogger(AuroraDialectUtils.class.getName()); + public AuroraDialectUtils(String writerIdQuery) { this.writerIdQuery = writerIdQuery; } - public @Nullable List processQueryResults(ResultSet resultSet) + public @Nullable List processTopologyResults(ResultSet resultSet) throws SQLException { if (resultSet.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 7aa9e1d7c..5b0551296 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -30,58 +30,39 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, BlueGreenDialect { - private static final String TOPOLOGY_QUERY = + protected static final String AURORA_VERSION_EXISTS_QUERY = "SHOW VARIABLES LIKE 'aurora_version'"; + protected static final String TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "CPU, REPLICA_LAG_IN_MILLISECONDS, LAST_UPDATE_TIMESTAMP " + "FROM information_schema.replica_host_status " // filter out instances that haven't been updated in the last 5 minutes + "WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID' "; - private static final String IS_WRITER_QUERY = + protected static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; + protected static final String IS_WRITER_QUERY = "SELECT SERVER_ID FROM information_schema.replica_host_status " - + "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id"; + + "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id"; + protected static final String IS_READER_QUERY = "SELECT @@innodb_read_only"; - private static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; - private static final String IS_READER_QUERY = "SELECT @@innodb_read_only"; - - private static final String BG_STATUS_QUERY = - "SELECT * FROM mysql.rds_topology"; - - private static final String TOPOLOGY_TABLE_EXIST_QUERY = + protected static final String BG_TOPOLOGY_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; + protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; - private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); + protected final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery("SHOW VARIABLES LIKE 'aurora_version'"); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(AURORA_VERSION_EXISTS_QUERY)) { if (rs.next()) { // If variable with such name is presented then it means it's an Aurora cluster return true; } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + return false; } @@ -102,30 +83,19 @@ public HostListProviderSupplier getHostListProvider() { } @Override - public String getBlueGreenStatusQuery() { - return BG_STATUS_QUERY; - } - - @Override - public boolean isBlueGreenStatusAvailable(final Connection connection) { - try { - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXIST_QUERY)) { - return rs.next(); - } - } catch (SQLException ex) { - return false; - } + public String getTopologyQuery() { + return TOPOLOGY_QUERY; } @Override - public String getTopologyQuery() { - return TOPOLOGY_QUERY; + public @Nullable List processTopologyResults(Connection conn, ResultSet rs) + throws SQLException { + return this.dialectUtils.processTopologyResults(rs); } @Override - public @Nullable List processQueryResults(Connection conn, ResultSet rs) throws SQLException { - return AuroraMysqlDialect.dialectUtils.processQueryResults(rs); + public String getInstanceIdQuery() { + return INSTANCE_ID_QUERY; } @Override @@ -139,8 +109,19 @@ public String getIsReaderQuery() { } @Override - public String getInstanceIdQuery() { - return INSTANCE_ID_QUERY; + public boolean isBlueGreenStatusAvailable(final Connection connection) { + try { + try (Statement statement = connection.createStatement(); + ResultSet rs = statement.executeQuery(BG_TOPOLOGY_EXISTS_QUERY)) { + return rs.next(); + } + } catch (SQLException ex) { + return false; + } } -} + @Override + public String getBlueGreenStatusQuery() { + return BG_STATUS_QUERY; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 26dd11dc4..5f1452069 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -29,21 +29,14 @@ import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; -/** - * Suitable for the following AWS PG configurations. - * - Regional Cluster - */ public class AuroraPgDialect extends PgDialect implements TopologyDialect, AuroraLimitlessDialect, BlueGreenDialect { - private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); - private static final String extensionsSql = + protected static final String AURORA_UTILS_EXIST_QUERY = "SELECT (setting LIKE '%aurora_stat_utils%') AS aurora_stat_utils " + "FROM pg_catalog.pg_settings " + "WHERE name OPERATOR(pg_catalog.=) 'rds.extensions'"; - - private static final String topologySql = "SELECT 1 FROM pg_catalog.aurora_replica_status() LIMIT 1"; - - private static final String TOPOLOGY_QUERY = + protected static final String TOPOLOGY_EXISTS_QUERY = "SELECT 1 FROM pg_catalog.aurora_replica_status() LIMIT 1"; + protected static final String TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "CPU, COALESCE(REPLICA_LAG_IN_MSEC, 0), LAST_UPDATE_TIMESTAMP " + "FROM pg_catalog.aurora_replica_status() " @@ -53,24 +46,25 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror + "OR SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " + "OR LAST_UPDATE_TIMESTAMP IS NULL"; - private static final String WRITER_ID_QUERY = + protected static final String INSTANCE_ID_QUERY = "SELECT pg_catalog.aurora_db_instance_identifier()"; + protected static final String WRITER_ID_QUERY = "SELECT SERVER_ID FROM pg_catalog.aurora_replica_status() " + "WHERE SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " + "AND SERVER_ID OPERATOR(pg_catalog.=) pg_catalog.aurora_db_instance_identifier()"; + protected static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; - private static final String INSTANCE_ID_QUERY = "SELECT pg_catalog.aurora_db_instance_identifier()"; - private static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; protected static final String LIMITLESS_ROUTER_ENDPOINT_QUERY = "select router_endpoint, load from pg_catalog.aurora_limitless_router_endpoints()"; - private static final String BG_STATUS_QUERY = + protected static final String BG_TOPOLOGY_EXISTS_QUERY = + "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc"; + protected static final String BG_STATUS_QUERY = "SELECT * FROM " + "pg_catalog.get_blue_green_fast_switchover_metadata('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - private static final String TOPOLOGY_TABLE_EXIST_QUERY = - "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc"; + private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); - private static final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); + protected final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); @Override public boolean isDialect(final Connection connection) { @@ -78,13 +72,9 @@ public boolean isDialect(final Connection connection) { return false; } - Statement stmt = null; - ResultSet rs = null; boolean hasExtensions = false; - boolean hasTopology = false; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(extensionsSql); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(AURORA_UTILS_EXIST_QUERY)) { if (rs.next()) { final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); LOGGER.finest(() -> String.format("auroraUtils: %b", auroraUtils)); @@ -93,53 +83,24 @@ public boolean isDialect(final Connection connection) { } } } catch (SQLException ex) { - // ignore - } finally { - // TODO: switch to try-with-resources here and check for any other places that can be cleaned up similarly - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + if (!hasExtensions) { return false; } - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(topologySql); + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(TOPOLOGY_EXISTS_QUERY)) { if (rs.next()) { LOGGER.finest(() -> "hasTopology: true"); - hasTopology = true; + return true; } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } - return hasExtensions && hasTopology; + + return false; } @Override @@ -159,8 +120,9 @@ public String getTopologyQuery() { } @Override - public String getIsReaderQuery() { - return IS_READER_QUERY; + public @Nullable List processTopologyResults(Connection conn, ResultSet rs) + throws SQLException { + return this.dialectUtils.processTopologyResults(rs); } @Override @@ -170,13 +132,12 @@ public String getInstanceIdQuery() { @Override public boolean isWriterInstance(Connection connection) throws SQLException { - return AuroraPgDialect.dialectUtils.isWriterInstance(connection); + return this.dialectUtils.isWriterInstance(connection); } @Override - public @Nullable List processQueryResults(Connection conn, ResultSet rs) - throws SQLException { - return AuroraPgDialect.dialectUtils.processQueryResults(rs); + public String getIsReaderQuery() { + return IS_READER_QUERY; } @Override @@ -184,20 +145,20 @@ public String getLimitlessRouterEndpointQuery() { return LIMITLESS_ROUTER_ENDPOINT_QUERY; } - @Override - public String getBlueGreenStatusQuery() { - return BG_STATUS_QUERY; - } - @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { try { try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXIST_QUERY)) { + ResultSet rs = statement.executeQuery(BG_TOPOLOGY_EXISTS_QUERY)) { return rs.next(); } } catch (SQLException ex) { return false; } } + + @Override + public String getBlueGreenStatusQuery() { + return BG_STATUS_QUERY; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/BlueGreenDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/BlueGreenDialect.java index ce1b678d3..a5e34f150 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/BlueGreenDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/BlueGreenDialect.java @@ -19,7 +19,7 @@ import java.sql.Connection; public interface BlueGreenDialect { - String getBlueGreenStatusQuery(); - boolean isBlueGreenStatusAvailable(final Connection connection); + + String getBlueGreenStatusQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java index 367db7d25..4fe1d7f8b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java @@ -26,22 +26,23 @@ import software.amazon.jdbc.plugin.failover.FailoverRestriction; public interface Dialect { - int getDefaultPort(); - - ExceptionHandler getExceptionHandler(); - - String getHostAliasQuery(); - - String getServerVersionQuery(); boolean isDialect(Connection connection); + int getDefaultPort(); + List getDialectUpdateCandidates(); + ExceptionHandler getExceptionHandler(); + HostListProviderSupplier getHostListProvider(); + String getHostAliasQuery(); + void prepareConnectProperties( final @NonNull Properties connectProperties, final @NonNull String protocol, final @NonNull HostSpec hostSpec); EnumSet getFailoverRestrictions(); + + String getServerVersionQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java index 3b368a8a1..fc017eb96 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java @@ -32,45 +32,23 @@ import software.amazon.jdbc.plugin.failover.FailoverRestriction; public class MariaDbDialect implements Dialect { + + protected static final String VERSION_QUERY = "SELECT VERSION()"; + protected static final String HOST_ALIAS_QUERY = "SELECT CONCAT(@@hostname, ':', @@port)"; + + private static MariaDBExceptionHandler mariaDBExceptionHandler; + private static final EnumSet NO_FAILOVER_RESTRICTIONS = + EnumSet.noneOf(FailoverRestriction.class); private static final List dialectUpdateCandidates = Arrays.asList( DialectCodes.AURORA_MYSQL, DialectCodes.RDS_MULTI_AZ_MYSQL_CLUSTER, DialectCodes.RDS_MYSQL, DialectCodes.MYSQL); - private static MariaDBExceptionHandler mariaDBExceptionHandler; - - private static final EnumSet NO_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); - - @Override - public int getDefaultPort() { - return 3306; - } - - @Override - public ExceptionHandler getExceptionHandler() { - if (mariaDBExceptionHandler == null) { - mariaDBExceptionHandler = new MariaDBExceptionHandler(); - } - return mariaDBExceptionHandler; - } - - @Override - public String getHostAliasQuery() { - return "SELECT CONCAT(@@hostname, ':', @@port)"; - } - - @Override - public String getServerVersionQuery() { - return "SELECT VERSION()"; - } @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.getServerVersionQuery()); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(VERSION_QUERY)) { while (rs.next()) { final String columnValue = rs.getString(1); if (columnValue != null && columnValue.toLowerCase().contains("mariadb")) { @@ -78,31 +56,30 @@ public boolean isDialect(final Connection connection) { } } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + return false; } + @Override + public int getDefaultPort() { + return 3306; + } + @Override public List getDialectUpdateCandidates() { return dialectUpdateCandidates; } + @Override + public ExceptionHandler getExceptionHandler() { + if (mariaDBExceptionHandler == null) { + mariaDBExceptionHandler = new MariaDBExceptionHandler(); + } + return mariaDBExceptionHandler; + } + public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); @@ -116,6 +93,16 @@ public void prepareConnectProperties( @Override public EnumSet getFailoverRestrictions() { - return NO_RESTRICTIONS; + return NO_FAILOVER_RESTRICTIONS; + } + + @Override + public String getServerVersionQuery() { + return VERSION_QUERY; + } + + @Override + public String getHostAliasQuery() { + return HOST_ALIAS_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java index 5cdcb5e66..42d25a3ab 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java @@ -26,22 +26,26 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; +import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; public class MultiAzDialectUtils { private static final Logger LOGGER = Logger.getLogger(MultiAzDialectUtils.class.getName()); + private final String instanceIdQuery; private final String writerIdQuery; private final String writerIdQueryColumn; - private final String instanceIdQuery; - public MultiAzDialectUtils(String writerIdQuery, String writerIdQueryColumn, String instanceIdQuery) { + public MultiAzDialectUtils(String instanceIdQuery, String writerIdQuery, String writerIdQueryColumn) { + this.instanceIdQuery = instanceIdQuery; this.writerIdQuery = writerIdQuery; this.writerIdQueryColumn = writerIdQueryColumn; - this.instanceIdQuery = instanceIdQuery; } - public @Nullable List processQueryResults(Connection conn, ResultSet resultSet) + public @Nullable List processTopologyResults(Connection conn, ResultSet resultSet) throws SQLException { List hosts = new ArrayList<>(); while (resultSet.next()) { @@ -105,4 +109,16 @@ public boolean isWriterInstance(final Connection connection) throws SQLException } return false; } + + protected HostListProviderSupplier getHostListProviderSupplier(TopologyDialect dialect) { + return (properties, initialUrl, servicesContainer) -> { + final PluginService pluginService = servicesContainer.getPluginService(); + if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { + return new MonitoringRdsHostListProvider(dialect, properties, initialUrl, servicesContainer); + + } else { + return new RdsHostListProvider(dialect, properties, initialUrl, servicesContainer); + } + }; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java index de9f181d3..05b23aa37 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java @@ -33,45 +33,23 @@ public class MysqlDialect implements Dialect { + protected static final String VERSION_QUERY = "SHOW VARIABLES LIKE 'version_comment'"; + protected static final String HOST_ALIAS_QUERY = "SELECT CONCAT(@@hostname, ':', @@port)"; + + private static MySQLExceptionHandler mySQLExceptionHandler; + private static final EnumSet NO_FAILOVER_RESTRICTIONS = + EnumSet.noneOf(FailoverRestriction.class); private static final List dialectUpdateCandidates = Arrays.asList( DialectCodes.RDS_MULTI_AZ_MYSQL_CLUSTER, DialectCodes.AURORA_MYSQL, DialectCodes.RDS_MYSQL ); - private static MySQLExceptionHandler mySQLExceptionHandler; - private static final EnumSet NO_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); - - @Override - public int getDefaultPort() { - return 3306; - } - - @Override - public ExceptionHandler getExceptionHandler() { - if (mySQLExceptionHandler == null) { - mySQLExceptionHandler = new MySQLExceptionHandler(); - } - return mySQLExceptionHandler; - } - - @Override - public String getHostAliasQuery() { - return "SELECT CONCAT(@@hostname, ':', @@port)"; - } - - @Override - public String getServerVersionQuery() { - return "SHOW VARIABLES LIKE 'version_comment'"; - } @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.getServerVersionQuery()); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(VERSION_QUERY)) { while (rs.next()) { final String columnValue = rs.getString(2); if (columnValue != null && columnValue.toLowerCase().contains("mysql")) { @@ -79,31 +57,30 @@ public boolean isDialect(final Connection connection) { } } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + return false; } + @Override + public int getDefaultPort() { + return 3306; + } + @Override public List getDialectUpdateCandidates() { return dialectUpdateCandidates; } + @Override + public ExceptionHandler getExceptionHandler() { + if (mySQLExceptionHandler == null) { + mySQLExceptionHandler = new MySQLExceptionHandler(); + } + return mySQLExceptionHandler; + } + public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); @@ -117,6 +94,16 @@ public void prepareConnectProperties( @Override public EnumSet getFailoverRestrictions() { - return NO_RESTRICTIONS; + return NO_FAILOVER_RESTRICTIONS; + } + + @Override + public String getServerVersionQuery() { + return VERSION_QUERY; + } + + @Override + public String getHostAliasQuery() { + return HOST_ALIAS_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java index 075cf242d..a612eb4e4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java @@ -36,74 +36,51 @@ */ public class PgDialect implements Dialect { + protected static final String PG_PROC_EXISTS_QUERY = "SELECT 1 FROM pg_catalog.pg_proc LIMIT 1"; + protected static final String VERSION_QUERY = "SELECT 'version', pg_catalog.VERSION()"; + protected static final String HOST_ALIAS_QUERY = + "SELECT pg_catalog.CONCAT(pg_catalog.inet_server_addr(), ':', pg_catalog.inet_server_port())"; + + private static PgExceptionHandler pgExceptionHandler; + private static final EnumSet NO_FAILOVER_RESTRICTIONS = + EnumSet.noneOf(FailoverRestriction.class); private static final List dialectUpdateCandidates = Arrays.asList( DialectCodes.AURORA_PG, DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, DialectCodes.RDS_PG); - private static PgExceptionHandler pgExceptionHandler; - - private static final EnumSet NO_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); - - @Override - public int getDefaultPort() { - return 5432; - } - - @Override - public ExceptionHandler getExceptionHandler() { - if (pgExceptionHandler == null) { - pgExceptionHandler = new PgExceptionHandler(); - } - return pgExceptionHandler; - } - - @Override - public String getHostAliasQuery() { - return "SELECT pg_catalog.CONCAT(pg_catalog.inet_server_addr(), ':', pg_catalog.inet_server_port())"; - } - - @Override - public String getServerVersionQuery() { - return "SELECT 'version', pg_catalog.VERSION()"; - } - @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery("SELECT 1 FROM pg_catalog.pg_proc LIMIT 1"); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(PG_PROC_EXISTS_QUERY)) { if (rs.next()) { return true; } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + return false; } + @Override + public int getDefaultPort() { + return 5432; + } + @Override public List getDialectUpdateCandidates() { return dialectUpdateCandidates; } + @Override + public ExceptionHandler getExceptionHandler() { + if (pgExceptionHandler == null) { + pgExceptionHandler = new PgExceptionHandler(); + } + return pgExceptionHandler; + } + @Override public HostListProviderSupplier getHostListProvider() { return (properties, initialUrl, servicesContainer) -> @@ -118,6 +95,16 @@ public void prepareConnectProperties( @Override public EnumSet getFailoverRestrictions() { - return NO_RESTRICTIONS; + return NO_FAILOVER_RESTRICTIONS; + } + + @Override + public String getServerVersionQuery() { + return VERSION_QUERY; + } + + @Override + public String getHostAliasQuery() { + return HOST_ALIAS_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java index c318b6b3d..493ccd5a1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java @@ -24,45 +24,38 @@ import java.util.List; import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverRestriction; -import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect implements TopologyDialect { - private static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; - - private static final String TOPOLOGY_TABLE_EXIST_QUERY = + protected static final String REPORT_HOST_EXISTS_QUERY = "SHOW VARIABLES LIKE 'report_host'"; + protected static final String TOPOLOGY_TABLE_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" - + " table_schema = 'mysql' AND table_name = 'rds_topology'"; + + " table_schema = 'mysql' AND table_name = 'rds_topology'"; + protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; + protected static final String INSTANCE_ID_QUERY = "SELECT @@server_id"; // For reader instances, the query returns a writer instance ID. For a writer instance, the query returns no data. - private static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; - - private static final String WRITER_ID_QUERY_COLUMN = "Source_Server_Id"; + protected static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; + protected static final String WRITER_ID_QUERY_COLUMN = "Source_Server_Id"; + protected static final String IS_READER_QUERY = "SELECT @@read_only"; - private static final String INSTANCE_ID_QUERY = "SELECT @@server_id"; - private static final String IS_READER_QUERY = "SELECT @@read_only"; - - private static final EnumSet RDS_MULTI_AZ_RESTRICTIONS = + private static final EnumSet FAILOVER_RESTRICTIONS = EnumSet.of(FailoverRestriction.DISABLE_TASK_A, FailoverRestriction.ENABLE_WRITER_IN_TASK_B); protected final RdsUtils rdsUtils = new RdsUtils(); protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN, INSTANCE_ID_QUERY); + INSTANCE_ID_QUERY, WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN); @Override public boolean isDialect(final Connection connection) { try { try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(TOPOLOGY_TABLE_EXIST_QUERY)) { + ResultSet rs = stmt.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { if (!rs.next()) { return false; } @@ -76,7 +69,7 @@ public boolean isDialect(final Connection connection) { } try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SHOW VARIABLES LIKE 'report_host'")) { + ResultSet rs = stmt.executeQuery(REPORT_HOST_EXISTS_QUERY)) { if (!rs.next()) { return false; } @@ -97,15 +90,7 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> { - final PluginService pluginService = servicesContainer.getPluginService(); - if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); - - } else { - return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); - } - }; + return this.dialectUtils.getHostListProviderSupplier(this); } @Override @@ -121,7 +106,7 @@ public void prepareConnectProperties( @Override public EnumSet getFailoverRestrictions() { - return RDS_MULTI_AZ_RESTRICTIONS; + return FAILOVER_RESTRICTIONS; } @Override @@ -130,9 +115,14 @@ public String getTopologyQuery() { } @Override - public List processQueryResults(Connection conn, ResultSet rs) + public List processTopologyResults(Connection conn, ResultSet rs) throws SQLException { - return dialectUtils.processQueryResults(conn, rs); + return dialectUtils.processTopologyResults(conn, rs); + } + + @Override + public String getInstanceIdQuery() { + return INSTANCE_ID_QUERY; } @Override @@ -144,9 +134,4 @@ public boolean isWriterInstance(Connection connection) throws SQLException { public String getIsReaderQuery() { return IS_READER_QUERY; } - - @Override - public String getInstanceIdQuery() { - return INSTANCE_ID_QUERY; - } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java index fe60cb2e1..8e0ea82a0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java @@ -21,56 +21,37 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.PluginService; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; -import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; public class RdsMultiAzDbClusterPgDialect extends PgDialect implements TopologyDialect { - private static final Logger LOGGER = Logger.getLogger(RdsMultiAzDbClusterPgDialect.class.getName()); - - private static MultiAzDbClusterPgExceptionHandler exceptionHandler; - - private static final String TOPOLOGY_QUERY = + protected static final String IS_RDS_CLUSTER_QUERY = + "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"; + protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; + protected static final String INSTANCE_ID_QUERY = "SELECT dbi_resource_id FROM rds_tools.dbi_resource_id()"; // For reader instances, the query should return a writer instance ID. // For a writer instance, the query should return no data. - private static final String WRITER_ID_QUERY = + protected static final String WRITER_ID_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()" + " WHERE multi_az_db_cluster_source_dbi_resource_id OPERATOR(pg_catalog.!=)" + " (SELECT dbi_resource_id FROM rds_tools.dbi_resource_id())"; + protected static final String WRITER_ID_QUERY_COLUMN = "multi_az_db_cluster_source_dbi_resource_id"; + protected static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; - private static final String IS_RDS_CLUSTER_QUERY = - "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"; - - private static final String WRITER_ID_QUERY_COLUMN = "multi_az_db_cluster_source_dbi_resource_id"; - - private static final String INSTANCE_ID_QUERY = "SELECT dbi_resource_id FROM rds_tools.dbi_resource_id()"; - - private static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; + private static MultiAzDbClusterPgExceptionHandler exceptionHandler; protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN, INSTANCE_ID_QUERY); - - @Override - public ExceptionHandler getExceptionHandler() { - if (exceptionHandler == null) { - exceptionHandler = new MultiAzDbClusterPgExceptionHandler(); - } - return exceptionHandler; - } + INSTANCE_ID_QUERY, WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN); @Override public boolean isDialect(final Connection connection) { try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(IS_RDS_CLUSTER_QUERY)) { + ResultSet rs = stmt.executeQuery(IS_RDS_CLUSTER_QUERY)) { return rs.next() && rs.getString(1) != null; } catch (final SQLException ex) { // ignore @@ -83,16 +64,17 @@ public boolean isDialect(final Connection connection) { return null; } + @Override + public ExceptionHandler getExceptionHandler() { + if (exceptionHandler == null) { + exceptionHandler = new MultiAzDbClusterPgExceptionHandler(); + } + return exceptionHandler; + } + @Override public HostListProviderSupplier getHostListProvider() { - return (properties, initialUrl, servicesContainer) -> { - final PluginService pluginService = servicesContainer.getPluginService(); - if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); - } else { - return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); - } - }; + return this.dialectUtils.getHostListProviderSupplier(this); } @Override @@ -101,23 +83,23 @@ public String getTopologyQuery() { } @Override - public boolean isWriterInstance(Connection connection) throws SQLException { - return dialectUtils.isWriterInstance(connection); + public @Nullable List processTopologyResults(Connection conn, ResultSet rs) + throws SQLException { + return this.dialectUtils.processTopologyResults(conn, rs); } @Override - public String getIsReaderQuery() { - return IS_READER_QUERY; + public String getInstanceIdQuery() { + return INSTANCE_ID_QUERY; } @Override - public String getInstanceIdQuery() { - return INSTANCE_ID_QUERY; + public boolean isWriterInstance(Connection connection) throws SQLException { + return dialectUtils.isWriterInstance(connection); } @Override - public @Nullable List processQueryResults(Connection conn, ResultSet rs) - throws SQLException { - return this.dialectUtils.processQueryResults(conn, rs); + public String getIsReaderQuery() { + return IS_READER_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java index 22e010ea7..8fa19292e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java @@ -26,13 +26,14 @@ public class RdsMysqlDialect extends MysqlDialect implements BlueGreenDialect { - private static final String BG_STATUS_QUERY = - "SELECT * FROM mysql.rds_topology"; - - private static final String TOPOLOGY_TABLE_EXIST_QUERY = + protected static final String REPORT_HOST_EXISTS_QUERY = "SHOW VARIABLES LIKE 'report_host'"; + protected static final String TOPOLOGY_TABLE_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; + protected static final String BG_STATUS_QUERY = + "SELECT * FROM mysql.rds_topology"; + private static final List dialectUpdateCandidates = Arrays.asList( DialectCodes.AURORA_MYSQL, DialectCodes.RDS_MULTI_AZ_MYSQL_CLUSTER); @@ -53,50 +54,34 @@ public boolean isDialect(final Connection connection) { // | Variable_name | value | // |-----------------|---------------------| // | version_comment | Source distribution | - // If super.idDialect returns true there is no need to check for RdsMysqlDialect. + // If super.isDialect returns true there is no need to check for RdsMysqlDialect. return false; } - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.getServerVersionQuery()); - if (!rs.next()) { - return false; - } - final String columnValue = rs.getString(2); - if (!"Source distribution".equalsIgnoreCase(columnValue)) { - return false; - } - - rs.close(); - rs = stmt.executeQuery("SHOW VARIABLES LIKE 'report_host'"); - if (!rs.next()) { - return false; - } - final String reportHost = rs.getString(2); // get variable value; expected empty value - return StringUtils.isNullOrEmpty(reportHost); + try (Statement stmt = connection.createStatement()) { + try (ResultSet rs = stmt.executeQuery(VERSION_QUERY)) { + if (!rs.next()) { + return false; + } - } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore + final String columnValue = rs.getString(2); + if (!"Source distribution".equalsIgnoreCase(columnValue)) { + return false; } } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore + + try (ResultSet rs = stmt.executeQuery(REPORT_HOST_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } + + final String reportHost = rs.getString(2); // get variable value; expected empty value + return StringUtils.isNullOrEmpty(reportHost); } + + } catch (final SQLException ex) { + return false; } - return false; } @Override @@ -104,20 +89,20 @@ public List getDialectUpdateCandidates() { return dialectUpdateCandidates; } - @Override - public String getBlueGreenStatusQuery() { - return BG_STATUS_QUERY; - } - @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { try { try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXIST_QUERY)) { + ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { return rs.next(); } } catch (SQLException ex) { return false; } } + + @Override + public String getBlueGreenStatusQuery() { + return BG_STATUS_QUERY; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java index d59b9f2eb..43cf1f5e9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java @@ -33,34 +33,29 @@ */ public class RdsPgDialect extends PgDialect implements BlueGreenDialect { - private static final Logger LOGGER = Logger.getLogger(RdsPgDialect.class.getName()); - - private static final List dialectUpdateCandidates = Arrays.asList( - DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, - DialectCodes.AURORA_PG); - - private static final String extensionsSql = "SELECT (setting LIKE '%rds_tools%') AS rds_tools, " + protected static final String EXTENSIONS_EXIST_SQL = "SELECT (setting LIKE '%rds_tools%') AS rds_tools, " + "(setting LIKE '%aurora_stat_utils%') AS aurora_stat_utils " + "FROM pg_catalog.pg_settings " + "WHERE name OPERATOR(pg_catalog.=) 'rds.extensions'"; + protected static final String TOPOLOGY_TABLE_EXISTS_QUERY = + "SELECT 'rds_tools.show_topology'::regproc"; - private static final String BG_STATUS_QUERY = + protected static final String BG_STATUS_QUERY = "SELECT * FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - private static final String TOPOLOGY_TABLE_EXIST_QUERY = - "SELECT 'rds_tools.show_topology'::regproc"; + private static final Logger LOGGER = Logger.getLogger(RdsPgDialect.class.getName()); + private static final List dialectUpdateCandidates = Arrays.asList( + DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, + DialectCodes.AURORA_PG); @Override public boolean isDialect(final Connection connection) { if (!super.isDialect(connection)) { return false; } - Statement stmt = null; - ResultSet rs = null; - try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(extensionsSql); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(EXTENSIONS_EXIST_SQL)) { while (rs.next()) { final boolean rdsTools = rs.getBoolean("rds_tools"); final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); @@ -70,23 +65,9 @@ public boolean isDialect(final Connection connection) { } } } catch (final SQLException ex) { - // ignore - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore - } - } - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore - } - } + return false; } + return false; } @@ -95,20 +76,20 @@ public List getDialectUpdateCandidates() { return dialectUpdateCandidates; } - @Override - public String getBlueGreenStatusQuery() { - return BG_STATUS_QUERY; - } - @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { try { try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXIST_QUERY)) { + ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { return rs.next(); } } catch (SQLException ex) { return false; } } + + @Override + public String getBlueGreenStatusQuery() { + return BG_STATUS_QUERY; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index f7cd8a694..f3fb5a9ff 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -26,12 +26,13 @@ public interface TopologyDialect extends Dialect { String getTopologyQuery(); @Nullable - List processQueryResults(Connection conn, ResultSet rs) throws SQLException; + List processTopologyResults(Connection conn, ResultSet rs) throws SQLException; - // TODO: can we remove this and use getHostRole instead? + String getInstanceIdQuery(); + + // TODO: dialects have an isWriterInstance method (uses is_writer query) and a getHostRole method + // (uses is_reader query). Can we merge them into one getHostRole method? boolean isWriterInstance(final Connection connection) throws SQLException; String getIsReaderQuery(); - - String getInstanceIdQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 262f65f23..b58a0bc3c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -26,7 +26,6 @@ import java.util.Objects; import java.util.Properties; import java.util.UUID; -import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; @@ -44,7 +43,6 @@ import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -import software.amazon.jdbc.util.SynchronousExecutor; import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; @@ -76,7 +74,6 @@ public class RdsHostListProvider implements DynamicHostListProvider { + "This pattern is required to be specified for IP address or custom domain connections to AWS RDS " + "clusters. Otherwise, if unspecified, the pattern will be automatically created for AWS RDS clusters."); - protected static final Executor networkTimeoutExecutor = new SynchronousExecutor(); protected static final RdsUtils rdsHelper = new RdsUtils(); protected static final ConnectionUrlParser connectionUrlParser = new ConnectionUrlParser(); protected static final int defaultTopologyQueryTimeoutMs = 5000; @@ -347,17 +344,6 @@ protected List queryForTopology(final Connection conn) throws SQLExcep return this.topologyUtils.queryForTopology(conn); } - /** - * Build a host dns endpoint based on host/node name. - * - * @param nodeName A host name. - * @return Host dns endpoint - */ - protected String getHostEndpoint(final String nodeName) { - final String host = this.clusterInstanceTemplate.getHost(); - return host.replace("?", nodeName); - } - /** * Get cached topology. * @@ -473,7 +459,6 @@ public HostRole getHostRole(Connection conn) throws SQLException { @Override public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { - // TODO: why do we return null in some unexpected scenarios and throw an exception in others? try { String instanceId = this.topologyUtils.getInstanceId(connection); if (instanceId == null) { @@ -488,6 +473,7 @@ public HostRole getHostRole(Connection conn) throws SQLException { } if (topology == null) { + // TODO: above, we throw an exception, but here, we return null. Should we stick with just one? return null; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index be4aa66d9..df15a2b80 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -22,7 +22,6 @@ import java.util.Properties; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.logging.Logger; import software.amazon.jdbc.AwsWrapperProperty; import software.amazon.jdbc.BlockingHostListProvider; import software.amazon.jdbc.HostSpec; @@ -36,10 +35,8 @@ import software.amazon.jdbc.util.monitoring.MonitorService; import software.amazon.jdbc.util.storage.StorageService; -public class MonitoringRdsHostListProvider extends RdsHostListProvider - implements BlockingHostListProvider, CanReleaseResources { - - private static final Logger LOGGER = Logger.getLogger(MonitoringRdsHostListProvider.class.getName()); +public class MonitoringRdsHostListProvider + extends RdsHostListProvider implements BlockingHostListProvider, CanReleaseResources { public static final AwsWrapperProperty CLUSTER_TOPOLOGY_HIGH_REFRESH_RATE_MS = new AwsWrapperProperty( diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java index 598dc73bb..83c5281d7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -73,8 +72,8 @@ public TopologyUtils( try (final Statement stmt = conn.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - List queryHosts = this.dialect.processQueryResults(conn, resultSet); - return this.processQueryResults(queryHosts); + List queryHosts = this.dialect.processTopologyResults(conn, resultSet); + return this.processTopologyResults(queryHosts); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -84,7 +83,7 @@ public TopologyUtils( } } - protected @Nullable List processQueryResults(@Nullable List queryHosts) { + protected @Nullable List processTopologyResults(@Nullable List queryHosts) { if (queryHosts == null) { return null; } @@ -147,6 +146,7 @@ protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { } catch (SQLException ex) { // do nothing } + return null; } From 80981657eeaa77eedf712d0c24eb32c6a4a8d9cc Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 30 Oct 2025 17:06:02 -0700 Subject: [PATCH 16/46] getHostListProvider -> getHostListProviderSupplier, RdsMultiAzDb -> MultiAz --- ...ClusterMysqlDialect.java => MultiAzClusterMysqlDialect.java} | 2 +- ...tiAzDbClusterPgDialect.java => MultiAzClusterPgDialect.java} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename wrapper/src/main/java/software/amazon/jdbc/dialect/{RdsMultiAzDbClusterMysqlDialect.java => MultiAzClusterMysqlDialect.java} (98%) rename wrapper/src/main/java/software/amazon/jdbc/dialect/{RdsMultiAzDbClusterPgDialect.java => MultiAzClusterPgDialect.java} (98%) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java similarity index 98% rename from wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java rename to wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 493ccd5a1..96673630f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -89,7 +89,7 @@ public boolean isDialect(final Connection connection) { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return this.dialectUtils.getHostListProviderSupplier(this); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java similarity index 98% rename from wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java rename to wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 8e0ea82a0..af805cda7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMultiAzDbClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -73,7 +73,7 @@ public ExceptionHandler getExceptionHandler() { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return this.dialectUtils.getHostListProviderSupplier(this); } From 983722cb83f9a068f207c7799762c1fd7194bcf6 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 30 Oct 2025 17:25:19 -0700 Subject: [PATCH 17/46] Checkstyle passing --- .../amazon/jdbc/PartialPluginService.java | 2 +- .../amazon/jdbc/PluginServiceImpl.java | 2 +- .../jdbc/dialect/AuroraMysqlDialect.java | 2 +- .../amazon/jdbc/dialect/AuroraPgDialect.java | 2 +- .../software/amazon/jdbc/dialect/Dialect.java | 2 +- .../amazon/jdbc/dialect/DialectManager.java | 4 ++-- .../amazon/jdbc/dialect/MariaDbDialect.java | 2 +- .../dialect/MultiAzClusterMysqlDialect.java | 2 +- .../jdbc/dialect/MultiAzClusterPgDialect.java | 2 +- .../amazon/jdbc/dialect/MysqlDialect.java | 2 +- .../amazon/jdbc/dialect/PgDialect.java | 2 +- .../amazon/jdbc/dialect/UnknownDialect.java | 2 +- .../jdbc/plugin/AbstractConnectionPlugin.java | 2 +- ...AuroraInitialConnectionStrategyPlugin.java | 2 +- .../jdbc/plugin/DefaultConnectionPlugin.java | 2 +- .../bluegreen/BlueGreenStatusMonitor.java | 4 ++-- .../failover/FailoverConnectionPlugin.java | 2 +- .../failover2/FailoverConnectionPlugin.java | 2 +- .../ReadWriteSplittingPlugin.java | 2 +- .../plugin/staledns/AuroraStaleDnsHelper.java | 2 +- .../plugin/staledns/AuroraStaleDnsPlugin.java | 2 +- .../HostResponseTimeServiceImpl.java | 1 - .../jdbc/util/FullServicesContainer.java | 2 +- .../jdbc/util/FullServicesContainerImpl.java | 2 +- .../amazon/jdbc/util/ServiceUtility.java | 4 ++-- .../jdbc/wrapper/ConnectionWrapper.java | 2 +- .../container/tests/PerformanceTest.java | 1 - .../amazon/jdbc/DialectDetectionTests.java | 8 ++++---- .../software/amazon/jdbc/DialectTests.java | 8 ++++---- .../jdbc/RoundRobinHostSelectorTest.java | 5 ----- .../amazon/jdbc/mock/TestPluginOne.java | 2 +- .../FailoverConnectionPluginTest.java | 2 +- .../LimitlessConnectionPluginTest.java | 2 +- .../LimitlessRouterServiceImplTest.java | 2 +- .../ReadWriteSplittingPluginTest.java | 2 +- .../hibernate_files/DataSourceTest.java | 7 +++---- ...stgreSQLCastingIntervalSecondJdbcType.java | 1 - .../PostgresIntervalSecondTest.java | 19 ++++++++---------- .../StructEmbeddableArrayTest.java | 20 ++++++++----------- 39 files changed, 60 insertions(+), 76 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 189c03c62..873d18b08 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -132,7 +132,7 @@ public PartialPluginService( ? this.configurationProfile.getExceptionHandler() : null; - HostListProviderSupplier supplier = this.dbDialect.getHostListProvider(); + HostListProviderSupplier supplier = this.dbDialect.getHostListProviderSupplier(); this.hostListProvider = supplier.getProvider(this.props, this.originalUrl, this.servicesContainer); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 69e4b6664..8d39ec768 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -729,7 +729,7 @@ public void updateDialect(final @NonNull Connection connection) throws SQLExcept return; } - final HostListProviderSupplier supplier = this.dialect.getHostListProvider(); + final HostListProviderSupplier supplier = this.dialect.getHostListProviderSupplier(); this.setHostListProvider(supplier.getProvider(this.props, this.originalUrl, this.servicesContainer)); this.refreshHostList(connection); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 5b0551296..b43129230 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -72,7 +72,7 @@ public boolean isDialect(final Connection connection) { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 5f1452069..62178449c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -104,7 +104,7 @@ public boolean isDialect(final Connection connection) { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java index 4fe1d7f8b..5f09aae0b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/Dialect.java @@ -35,7 +35,7 @@ public interface Dialect { ExceptionHandler getExceptionHandler(); - HostListProviderSupplier getHostListProvider(); + HostListProviderSupplier getHostListProviderSupplier(); String getHostAliasQuery(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java index d29a1b3dd..23c39a7a5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java @@ -57,9 +57,9 @@ public class DialectManager implements DialectProvider { put(DialectCodes.PG, new PgDialect()); put(DialectCodes.MARIADB, new MariaDbDialect()); put(DialectCodes.RDS_MYSQL, new RdsMysqlDialect()); - put(DialectCodes.RDS_MULTI_AZ_MYSQL_CLUSTER, new RdsMultiAzDbClusterMysqlDialect()); + put(DialectCodes.RDS_MULTI_AZ_MYSQL_CLUSTER, new MultiAzClusterMysqlDialect()); put(DialectCodes.RDS_PG, new RdsPgDialect()); - put(DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, new RdsMultiAzDbClusterPgDialect()); + put(DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, new MultiAzClusterPgDialect()); put(DialectCodes.AURORA_MYSQL, new AuroraMysqlDialect()); put(DialectCodes.AURORA_PG, new AuroraPgDialect()); put(DialectCodes.UNKNOWN, new UnknownDialect()); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java index fc017eb96..58453f6fa 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MariaDbDialect.java @@ -80,7 +80,7 @@ public ExceptionHandler getExceptionHandler() { return mariaDBExceptionHandler; } - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 96673630f..85428a18a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -30,7 +30,7 @@ import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class RdsMultiAzDbClusterMysqlDialect extends MysqlDialect implements TopologyDialect { +public class MultiAzClusterMysqlDialect extends MysqlDialect implements TopologyDialect { protected static final String REPORT_HOST_EXISTS_QUERY = "SHOW VARIABLES LIKE 'report_host'"; protected static final String TOPOLOGY_TABLE_EXISTS_QUERY = diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index af805cda7..50e3a495a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -26,7 +26,7 @@ import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; import software.amazon.jdbc.util.DriverInfo; -public class RdsMultiAzDbClusterPgDialect extends PgDialect implements TopologyDialect { +public class MultiAzClusterPgDialect extends PgDialect implements TopologyDialect { protected static final String IS_RDS_CLUSTER_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java index 05b23aa37..ae07fc808 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java @@ -81,7 +81,7 @@ public ExceptionHandler getExceptionHandler() { return mySQLExceptionHandler; } - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java index a612eb4e4..c136416e8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java @@ -82,7 +82,7 @@ public ExceptionHandler getExceptionHandler() { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/UnknownDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/UnknownDialect.java index 65b9eb544..05085bb3e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/UnknownDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/UnknownDialect.java @@ -80,7 +80,7 @@ public List getDialectUpdateCandidates() { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> new ConnectionStringHostListProvider(properties, initialUrl, servicesContainer.getHostListProviderService()); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java index 22c5b13aa..9cfb8de24 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AbstractConnectionPlugin.java @@ -24,12 +24,12 @@ import java.util.Properties; import java.util.Set; import software.amazon.jdbc.ConnectionPlugin; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.OldConnectionSuggestedAction; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; public abstract class AbstractConnectionPlugin implements ConnectionPlugin { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java index a828183e1..4464a56d5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/AuroraInitialConnectionStrategyPlugin.java @@ -27,13 +27,13 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Logger; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java index b69c1cdad..adc955484 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/DefaultConnectionPlugin.java @@ -34,7 +34,6 @@ import software.amazon.jdbc.ConnectionPlugin; import software.amazon.jdbc.ConnectionProvider; import software.amazon.jdbc.ConnectionProviderManager; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; @@ -43,6 +42,7 @@ import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.SqlMethodAnalyzer; import software.amazon.jdbc.util.WrapperUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java index 637791549..832e06056 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenStatusMonitor.java @@ -43,12 +43,12 @@ import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.dialect.BlueGreenDialect; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.plugin.iam.IamAuthConnectionPlugin; import software.amazon.jdbc.util.ConnectionUrlParser; @@ -626,7 +626,7 @@ protected void initHostListProvider() { if (connectionHostSpecCopy != null) { String hostListProviderUrl = String.format("%s%s/", protocol, connectionHostSpecCopy.getHostAndPort()); this.hostListProvider = this.pluginService.getDialect() - .getHostListProvider() + .getHostListProviderSupplier() .getProvider( hostListProperties, hostListProviderUrl, diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 30fcb0701..88f68e72b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -33,7 +33,6 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; @@ -43,6 +42,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index c0b1a7660..57cfb80c7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -31,7 +31,6 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; @@ -40,6 +39,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverFailedSQLException; import software.amazon.jdbc.plugin.failover.FailoverMode; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index 27263c19c..adeaa7365 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -28,7 +28,6 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; @@ -38,6 +37,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.cleanup.CanReleaseResources; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverSQLException; import software.amazon.jdbc.util.Messages; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index 6050df3ef..bdcb227a4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -25,12 +25,12 @@ import java.util.Map; import java.util.Properties; import java.util.logging.Logger; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java index b3cea1d1d..936ee1233 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsPlugin.java @@ -25,12 +25,12 @@ import java.util.Properties; import java.util.Set; import java.util.logging.Logger; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.JdbcCallable; import software.amazon.jdbc.JdbcMethod; import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; /** diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/strategy/fastestresponse/HostResponseTimeServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/strategy/fastestresponse/HostResponseTimeServiceImpl.java index 9915391ac..4d90f6d5d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/strategy/fastestresponse/HostResponseTimeServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/strategy/fastestresponse/HostResponseTimeServiceImpl.java @@ -28,7 +28,6 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.storage.SlidingExpirationCacheWithCleanupThread; public class HostResponseTimeServiceImpl implements HostResponseTimeService { diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java index cbe122ed8..4420bab7d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainer.java @@ -18,9 +18,9 @@ import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.util.monitoring.MonitorService; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.telemetry.TelemetryFactory; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java index 349239668..44cfda664 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/FullServicesContainerImpl.java @@ -18,9 +18,9 @@ import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.util.monitoring.MonitorService; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.telemetry.TelemetryFactory; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java b/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java index e06261bcb..3e4b134a1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/ServiceUtility.java @@ -21,11 +21,11 @@ import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.ConnectionPluginManager; import software.amazon.jdbc.ConnectionProvider; -import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.PartialPluginService; import software.amazon.jdbc.PluginServiceImpl; import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.dialect.HostListProviderSupplier; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.profile.ConfigurationProfile; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.monitoring.MonitorService; @@ -73,7 +73,7 @@ public FullServicesContainer createStandardServiceContainer( servicesContainer.setPluginManagerService(pluginService); pluginManager.initPlugins(servicesContainer, configurationProfile); - final HostListProviderSupplier supplier = pluginService.getDialect().getHostListProvider(); + final HostListProviderSupplier supplier = pluginService.getDialect().getHostListProviderSupplier(); if (supplier != null) { final HostListProvider provider = supplier.getProvider(props, originalUrl, servicesContainer); pluginService.setHostListProvider(provider); diff --git a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java index a57d23183..dd37a2cd0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/wrapper/ConnectionWrapper.java @@ -39,12 +39,12 @@ import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.ConnectionPluginManager; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.JdbcMethod; import software.amazon.jdbc.PluginManagerService; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.cleanup.CanReleaseResources; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.profile.ConfigurationProfile; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; diff --git a/wrapper/src/test/java/integration/container/tests/PerformanceTest.java b/wrapper/src/test/java/integration/container/tests/PerformanceTest.java index a1c1bcd72..cc264dfb4 100644 --- a/wrapper/src/test/java/integration/container/tests/PerformanceTest.java +++ b/wrapper/src/test/java/integration/container/tests/PerformanceTest.java @@ -62,7 +62,6 @@ import org.junit.jupiter.params.provider.Arguments; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.OpenedConnectionTracker; import software.amazon.jdbc.plugin.efm.HostMonitorThreadContainer; diff --git a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java index d86238214..49cde84b1 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java @@ -43,10 +43,10 @@ import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.dialect.DialectManager; import software.amazon.jdbc.dialect.MariaDbDialect; +import software.amazon.jdbc.dialect.MultiAzClusterMysqlDialect; +import software.amazon.jdbc.dialect.MultiAzClusterPgDialect; import software.amazon.jdbc.dialect.MysqlDialect; import software.amazon.jdbc.dialect.PgDialect; -import software.amazon.jdbc.dialect.RdsMultiAzDbClusterMysqlDialect; -import software.amazon.jdbc.dialect.RdsMultiAzDbClusterPgDialect; import software.amazon.jdbc.dialect.RdsMysqlDialect; import software.amazon.jdbc.dialect.RdsPgDialect; import software.amazon.jdbc.exceptions.ExceptionManager; @@ -220,7 +220,7 @@ void testUpdateDialectPgToTaz() throws SQLException { final PluginServiceImpl target = getPluginService(LOCALHOST, PG_PROTOCOL); target.setInitialConnectionHostSpec(mockHost); target.updateDialect(mockConnection); - assertEquals(RdsMultiAzDbClusterPgDialect.class, target.dialect.getClass()); + assertEquals(MultiAzClusterPgDialect.class, target.dialect.getClass()); } @Test @@ -273,7 +273,7 @@ void testUpdateDialectMariaToMysqlTaz() throws SQLException { final PluginServiceImpl target = getPluginService(LOCALHOST, MARIA_PROTOCOL); target.setInitialConnectionHostSpec(mockHost); target.updateDialect(mockConnection); - assertEquals(RdsMultiAzDbClusterMysqlDialect.class, target.dialect.getClass()); + assertEquals(MultiAzClusterMysqlDialect.class, target.dialect.getClass()); } @Test diff --git a/wrapper/src/test/java/software/amazon/jdbc/DialectTests.java b/wrapper/src/test/java/software/amazon/jdbc/DialectTests.java index 4170f8556..e3c2df258 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/DialectTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/DialectTests.java @@ -36,10 +36,10 @@ import software.amazon.jdbc.dialect.AuroraMysqlDialect; import software.amazon.jdbc.dialect.AuroraPgDialect; import software.amazon.jdbc.dialect.MariaDbDialect; +import software.amazon.jdbc.dialect.MultiAzClusterMysqlDialect; +import software.amazon.jdbc.dialect.MultiAzClusterPgDialect; import software.amazon.jdbc.dialect.MysqlDialect; import software.amazon.jdbc.dialect.PgDialect; -import software.amazon.jdbc.dialect.RdsMultiAzDbClusterMysqlDialect; -import software.amazon.jdbc.dialect.RdsMultiAzDbClusterPgDialect; import software.amazon.jdbc.dialect.RdsMysqlDialect; import software.amazon.jdbc.dialect.RdsPgDialect; @@ -51,11 +51,11 @@ public class DialectTests { @Mock private ResultSetMetaData mockResultSetMetaData; private final MysqlDialect mysqlDialect = new MysqlDialect(); private final RdsMysqlDialect rdsMysqlDialect = new RdsMysqlDialect(); - private final RdsMultiAzDbClusterMysqlDialect rdsTazMysqlDialect = new RdsMultiAzDbClusterMysqlDialect(); + private final MultiAzClusterMysqlDialect rdsTazMysqlDialect = new MultiAzClusterMysqlDialect(); private final AuroraMysqlDialect auroraMysqlDialect = new AuroraMysqlDialect(); private final PgDialect pgDialect = new PgDialect(); private final RdsPgDialect rdsPgDialect = new RdsPgDialect(); - private final RdsMultiAzDbClusterPgDialect rdsTazPgDialect = new RdsMultiAzDbClusterPgDialect(); + private final MultiAzClusterPgDialect rdsTazPgDialect = new MultiAzClusterPgDialect(); private final AuroraPgDialect auroraPgDialect = new AuroraPgDialect(); private final MariaDbDialect mariaDbDialect = new MariaDbDialect(); private AutoCloseable closeable; diff --git a/wrapper/src/test/java/software/amazon/jdbc/RoundRobinHostSelectorTest.java b/wrapper/src/test/java/software/amazon/jdbc/RoundRobinHostSelectorTest.java index 7a523a3e7..3f61a1ec8 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/RoundRobinHostSelectorTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/RoundRobinHostSelectorTest.java @@ -27,12 +27,7 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.HostSpecBuilder; -import software.amazon.jdbc.RoundRobinHostSelector; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; -import software.amazon.jdbc.util.HostSelectorUtils; public class RoundRobinHostSelectorTest { private static final int TEST_PORT = 5432; diff --git a/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java b/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java index 36f077f73..523d3be59 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java +++ b/wrapper/src/test/java/software/amazon/jdbc/mock/TestPluginOne.java @@ -27,7 +27,6 @@ import java.util.Properties; import java.util.Set; import software.amazon.jdbc.ConnectionPlugin; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; @@ -35,6 +34,7 @@ import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.OldConnectionSuggestedAction; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; public class TestPluginOne implements ConnectionPlugin { diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java index 859472bb4..2c72ed243 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPluginTest.java @@ -49,7 +49,6 @@ import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; @@ -58,6 +57,7 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java index 13de74a6b..e6e498a41 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessConnectionPluginTest.java @@ -35,7 +35,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; -import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; @@ -45,6 +44,7 @@ import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.dialect.PgDialect; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProvider; public class LimitlessConnectionPluginTest { diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java index 0b3e7c2a5..81559231c 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterServiceImplTest.java @@ -39,7 +39,6 @@ import org.mockito.MockitoAnnotations; import software.amazon.jdbc.ConnectionProvider; import software.amazon.jdbc.HighestWeightHostSelector; -import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; @@ -48,6 +47,7 @@ import software.amazon.jdbc.WeightedRandomHostSelector; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProvider; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.FullServicesContainerImpl; import software.amazon.jdbc.util.events.EventPublisher; diff --git a/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java b/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java index 834e932d2..683346da3 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPluginTest.java @@ -45,7 +45,6 @@ import org.junit.jupiter.api.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; @@ -56,6 +55,7 @@ import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.dialect.Dialect; import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.failover.FailoverSuccessSQLException; import software.amazon.jdbc.util.SqlState; diff --git a/wrapper/src/test/resources/hibernate_files/DataSourceTest.java b/wrapper/src/test/resources/hibernate_files/DataSourceTest.java index 624114400..38a6b1c78 100644 --- a/wrapper/src/test/resources/hibernate_files/DataSourceTest.java +++ b/wrapper/src/test/resources/hibernate_files/DataSourceTest.java @@ -4,6 +4,9 @@ */ package org.hibernate.orm.test.datasource; +import static org.hibernate.internal.util.StringHelper.split; +import static org.junit.jupiter.api.Assertions.assertTrue; + import jakarta.persistence.Entity; import jakarta.persistence.Id; import org.hibernate.cfg.Environment; @@ -21,10 +24,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - -import static org.hibernate.internal.util.StringHelper.split; -import static org.junit.jupiter.api.Assertions.assertTrue; - @Jpa(annotatedClasses = DataSourceTest.TestEntity.class, integrationSettings = @Setting(name = JdbcSettings.CONNECTION_PROVIDER, value = "org.hibernate.orm.test.datasource.TestDataSourceConnectionProvider")) diff --git a/wrapper/src/test/resources/hibernate_files/PostgreSQLCastingIntervalSecondJdbcType.java b/wrapper/src/test/resources/hibernate_files/PostgreSQLCastingIntervalSecondJdbcType.java index 7dcd98188..55f5386bf 100644 --- a/wrapper/src/test/resources/hibernate_files/PostgreSQLCastingIntervalSecondJdbcType.java +++ b/wrapper/src/test/resources/hibernate_files/PostgreSQLCastingIntervalSecondJdbcType.java @@ -9,7 +9,6 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; - import org.hibernate.dialect.Dialect; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.JdbcMappingContainer; diff --git a/wrapper/src/test/resources/hibernate_files/PostgresIntervalSecondTest.java b/wrapper/src/test/resources/hibernate_files/PostgresIntervalSecondTest.java index f07faccac..065b83fb3 100644 --- a/wrapper/src/test/resources/hibernate_files/PostgresIntervalSecondTest.java +++ b/wrapper/src/test/resources/hibernate_files/PostgresIntervalSecondTest.java @@ -6,33 +6,30 @@ import static org.assertj.core.api.Assertions.assertThat; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import java.time.Duration; - import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.PostgreSQLDialect; import org.hibernate.dialect.type.PostgreSQLIntervalSecondJdbcType; import org.hibernate.metamodel.spi.MappingMetamodelImplementor; import org.hibernate.persister.entity.EntityPersister; -import org.hibernate.type.SqlTypes; -import org.hibernate.type.descriptor.jdbc.JdbcType; -import org.hibernate.type.descriptor.jdbc.NumericJdbcType; -import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; - import org.hibernate.testing.orm.junit.DomainModel; import org.hibernate.testing.orm.junit.RequiresDialect; import org.hibernate.testing.orm.junit.ServiceRegistry; import org.hibernate.testing.orm.junit.SessionFactory; import org.hibernate.testing.orm.junit.SessionFactoryScope; import org.hibernate.testing.orm.junit.Setting; +import org.hibernate.type.SqlTypes; +import org.hibernate.type.descriptor.jdbc.JdbcType; +import org.hibernate.type.descriptor.jdbc.NumericJdbcType; +import org.hibernate.type.descriptor.jdbc.spi.JdbcTypeRegistry; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - /** * Test to see if using `@org.hibernate.annotations.JdbcTypeCode` or `@org.hibernate.annotations.JdbcType` * will override a default JdbcType set by a {@link AvailableSettings#PREFERRED_DURATION_JDBC_TYPE config property}. diff --git a/wrapper/src/test/resources/hibernate_files/StructEmbeddableArrayTest.java b/wrapper/src/test/resources/hibernate_files/StructEmbeddableArrayTest.java index c7c7faa20..46cc08277 100644 --- a/wrapper/src/test/resources/hibernate_files/StructEmbeddableArrayTest.java +++ b/wrapper/src/test/resources/hibernate_files/StructEmbeddableArrayTest.java @@ -4,6 +4,14 @@ */ package org.hibernate.orm.test.mapping.embeddable; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNull; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ParameterMode; +import jakarta.persistence.Tuple; import java.net.URL; import java.sql.Time; import java.sql.Timestamp; @@ -18,7 +26,6 @@ import java.util.List; import java.util.Set; import java.util.UUID; - import org.hibernate.annotations.Struct; import org.hibernate.boot.ResourceStreamLocator; import org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl; @@ -35,7 +42,6 @@ import org.hibernate.dialect.PostgresPlusDialect; import org.hibernate.procedure.ProcedureCall; import org.hibernate.procedure.ProcedureParameter; - import org.hibernate.testing.jdbc.SharedDriverManagerTypeCacheClearingIntegrator; import org.hibernate.testing.orm.domain.gambit.EntityOfBasics; import org.hibernate.testing.orm.domain.gambit.MutableValue; @@ -54,16 +60,6 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.ParameterMode; -import jakarta.persistence.Tuple; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNull; - @BootstrapServiceRegistry( javaServices = @BootstrapServiceRegistry.JavaService( role = AdditionalMappingContributor.class, From 3608e73ff067e3db0b3edd506ce8f80a57c3d17d Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 30 Oct 2025 17:45:25 -0700 Subject: [PATCH 18/46] wip unit tests failing --- .../hostlistprovider/RdsHostListProvider.java | 23 +++++++++++++++---- .../RdsHostListProviderTest.java | 9 ++++++-- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index b58a0bc3c..825125352 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -121,6 +121,17 @@ public RdsHostListProvider( this.hostListProviderService = servicesContainer.getHostListProviderService(); } + // For testing purposes only + public RdsHostListProvider( + final TopologyDialect dialect, + final Properties properties, + final String originalUrl, + final FullServicesContainer servicesContainer, + final TopologyUtils topologyUtils) { + this(dialect, properties, originalUrl, servicesContainer); + this.topologyUtils = topologyUtils; + } + protected void init() throws SQLException { if (this.isInitialized) { return; @@ -192,11 +203,13 @@ protected void init() throws SQLException { } } - this.topologyUtils = new TopologyUtils( - this.dialect, - this.clusterInstanceTemplate, - this.initialHostSpec, - this.servicesContainer.getPluginService().getHostSpecBuilder()); + if (this.topologyUtils == null) { + this.topologyUtils = new TopologyUtils( + this.dialect, + this.clusterInstanceTemplate, + this.initialHostSpec, + this.servicesContainer.getPluginService().getHostSpecBuilder()); + } this.isInitialized = true; } finally { diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index e5e00aeaf..778d1c16a 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -64,6 +64,7 @@ import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.events.EventPublisher; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.storage.TestStorageServiceImpl; @@ -78,7 +79,9 @@ class RdsHostListProviderTest { @Mock private FullServicesContainer mockServicesContainer; @Mock private PluginService mockPluginService; @Mock private HostListProviderService mockHostListProviderService; + @Mock private HostSpecBuilder mockHostSpecBuilder; @Mock private EventPublisher mockEventPublisher; + @Mock private TopologyUtils mockTopologyUtils; @Mock private TopologyDialect mockDialect; @Captor private ArgumentCaptor queryCaptor; @@ -95,9 +98,11 @@ void setUp() throws SQLException { storageService = new TestStorageServiceImpl(mockEventPublisher); when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); when(mockServicesContainer.getStorageService()).thenReturn(storageService); + when(mockServicesContainer.getPluginService()).thenReturn(mockPluginService); when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); + when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); when(mockConnection.createStatement()).thenReturn(mockStatement); when(mockStatement.executeQuery(queryCaptor.capture())).thenReturn(mockResultSet); when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); @@ -114,8 +119,8 @@ void tearDown() throws Exception { } private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { - RdsHostListProvider provider = - new RdsHostListProvider(mockDialect, new Properties(), originalUrl, mockServicesContainer); + RdsHostListProvider provider = new RdsHostListProvider( + mockDialect, new Properties(), originalUrl, mockServicesContainer, mockTopologyUtils); provider.init(); return provider; } From 73a9b87a150863169df381cada72588c50c3a403 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 31 Oct 2025 09:51:33 -0700 Subject: [PATCH 19/46] Move TopologyUtils to hostlistprovider package --- .../amazon/jdbc/hostlistprovider/RdsHostListProvider.java | 1 - .../jdbc/{util => hostlistprovider}/TopologyUtils.java | 6 ++++-- .../monitoring/ClusterTopologyMonitorImpl.java | 3 +-- .../jdbc/hostlistprovider/RdsHostListProviderTest.java | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) rename wrapper/src/main/java/software/amazon/jdbc/{util => hostlistprovider}/TopologyUtils.java (97%) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 825125352..1adb1a6c4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -43,7 +43,6 @@ import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java similarity index 97% rename from wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index 83c5281d7..542783f5c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -15,7 +15,7 @@ */ -package software.amazon.jdbc.util; +package software.amazon.jdbc.hostlistprovider; import java.sql.Connection; import java.sql.ResultSet; @@ -35,6 +35,8 @@ import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.dialect.TopologyQueryHostSpec; import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.SynchronousExecutor; public class TopologyUtils { private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); @@ -144,7 +146,7 @@ protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { } } } catch (SQLException ex) { - // do nothing + return null; } return null; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 7ab890ef5..ccb74f843 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -49,7 +49,7 @@ import software.amazon.jdbc.util.ServiceUtility; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.SynchronousExecutor; -import software.amazon.jdbc.util.TopologyUtils; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.monitoring.AbstractMonitor; @@ -614,7 +614,6 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { } return hosts; } catch (SQLException ex) { - // do nothing LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.errorFetchingTopology", new Object[]{ex})); } return null; diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 778d1c16a..9ee39e1b5 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -64,7 +64,6 @@ import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.TopologyUtils; import software.amazon.jdbc.util.events.EventPublisher; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.storage.TestStorageServiceImpl; From 54aa6324a2373e745e1a41823b20ad2109fdcb4e Mon Sep 17 00:00:00 2001 From: sergiyvamz <75754709+sergiyvamz@users.noreply.github.com> Date: Fri, 31 Oct 2025 14:13:53 -0700 Subject: [PATCH 20/46] add support of the following domains: eu, au, uk (#1587) --- .../software/amazon/jdbc/util/RdsUtils.java | 21 ++++++++++--------- .../amazon/jdbc/util/RdsUtilsTests.java | 14 +++++++++++++ 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java index e9177a277..2da967a9b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java @@ -79,7 +79,7 @@ public class RdsUtils { "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.rds\\.amazonaws\\.com\\.?)$", + + "\\.(rds|rds-fips)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_CLUSTER_PATTERN = @@ -87,20 +87,21 @@ public class RdsUtils { "^(?.+)\\." + "(?cluster-|cluster-ro-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.rds\\.amazonaws\\.com\\.?)$", + + "\\.(rds|rds-fips)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_LIMITLESS_CLUSTER_PATTERN = Pattern.compile( "(?.+)\\." + "(?shardgrp-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.rds\\.(amazonaws\\.com\\.?|amazonaws\\.com\\.cn\\.?|sc2s\\.sgov\\.gov\\.?|c2s\\.ic\\.gov\\.?))$", + + "\\.(rds|rds-fips)\\.(amazonaws\\.com\\.?|amazonaws\\.eu\\.?|amazonaws\\.au\\.?|amazonaws\\.uk\\.?" + + "|amazonaws\\.com\\.cn\\.?|sc2s\\.sgov\\.gov\\.?|c2s\\.ic\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_CHINA_DNS_PATTERN = Pattern.compile( "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" - + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + "\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); @@ -108,7 +109,7 @@ public class RdsUtils { Pattern.compile( "^(?.+)\\." + "(?cluster-|cluster-ro-)+" - + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + "\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); @@ -117,7 +118,7 @@ public class RdsUtils { "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.rds\\.amazonaws\\.com\\.cn\\.?)$", + + "\\.(rds|rds-fips)\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_OLD_CHINA_CLUSTER_PATTERN = @@ -125,14 +126,14 @@ public class RdsUtils { "^(?.+)\\." + "(?cluster-|cluster-ro-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.rds\\.amazonaws\\.com\\.cn\\.?)$", + + "\\.(rds|rds-fips)\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_GOV_DNS_PATTERN = Pattern.compile( "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" - + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + "\\.(amazonaws\\.com\\.?|c2s\\.ic\\.gov\\.?|sc2s\\.sgov\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); @@ -140,14 +141,14 @@ public class RdsUtils { Pattern.compile( "^(?.+)\\." + "(?cluster-|cluster-ro-)+" - + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + "\\.(amazonaws\\.com\\.?|c2s\\.ic\\.gov\\.?|sc2s\\.sgov\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); private static final Pattern ELB_PATTERN = Pattern.compile( "^(?.+)\\.elb\\." - + "((?[a-zA-Z0-9\\-]+)\\.amazonaws\\.com\\.?)$", + + "((?[a-zA-Z0-9\\-]+)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern IP_V4 = diff --git a/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java b/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java index 59ee12619..d2ef2e1eb 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java @@ -42,6 +42,9 @@ public class RdsUtilsTests { private static final String usEastRegionLimitlessDbShardGroup = "database-test-name.shardgrp-XYZ.us-east-2.rds.amazonaws.com"; + private static final String euRedshift = + "redshift-test-name.XYZ.eusc-de-east-1.rds.amazonaws.eu"; + private static final String chinaRegionCluster = "database-test-name.cluster-XYZ.rds.cn-northwest-1.amazonaws.com.cn"; private static final String chinaRegionClusterTrailingDot = @@ -133,6 +136,7 @@ public void testIsRdsDns() { assertFalse(target.isRdsDns(usEastRegionElbUrl)); assertFalse(target.isRdsDns(usEastRegionElbUrlTrailingDot)); assertTrue(target.isRdsDns(usEastRegionLimitlessDbShardGroup)); + assertTrue(target.isRdsDns(euRedshift)); assertTrue(target.isRdsDns(chinaRegionCluster)); assertTrue(target.isRdsDns(chinaRegionClusterTrailingDot)); @@ -210,6 +214,9 @@ public void testGetRdsInstanceHostPattern() { assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionProxy)); assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionCustomDomain)); assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionLimitlessDbShardGroup)); + + final String euRedshiftExpectedHostPattern = "?.XYZ.eusc-de-east-1.rds.amazonaws.eu"; + assertEquals(euRedshiftExpectedHostPattern, target.getRdsInstanceHostPattern(euRedshift)); } @Test @@ -221,6 +228,7 @@ public void testIsRdsClusterDns() { assertFalse(target.isRdsClusterDns(usEastRegionCustomDomain)); assertFalse(target.isRdsClusterDns(usEastRegionElbUrl)); assertFalse(target.isRdsClusterDns(usEastRegionLimitlessDbShardGroup)); + assertFalse(target.isRdsClusterDns(euRedshift)); assertTrue(target.isRdsClusterDns(usIsobEastRegionCluster)); assertTrue(target.isRdsClusterDns(usIsobEastRegionClusterReadOnly)); @@ -260,6 +268,7 @@ public void testIsWriterClusterDns() { assertFalse(target.isWriterClusterDns(usEastRegionCustomDomain)); assertFalse(target.isWriterClusterDns(usEastRegionElbUrl)); assertFalse(target.isWriterClusterDns(usEastRegionLimitlessDbShardGroup)); + assertFalse(target.isWriterClusterDns(euRedshift)); assertTrue(target.isWriterClusterDns(usIsobEastRegionCluster)); assertFalse(target.isWriterClusterDns(usIsobEastRegionClusterReadOnly)); @@ -299,6 +308,7 @@ public void testIsReaderClusterDns() { assertFalse(target.isReaderClusterDns(usEastRegionCustomDomain)); assertFalse(target.isReaderClusterDns(usEastRegionElbUrl)); assertFalse(target.isReaderClusterDns(usEastRegionLimitlessDbShardGroup)); + assertFalse(target.isReaderClusterDns(euRedshift)); assertFalse(target.isReaderClusterDns(usIsobEastRegionCluster)); assertTrue(target.isReaderClusterDns(usIsobEastRegionClusterReadOnly)); @@ -338,6 +348,7 @@ public void testIsLimitlessDbShardGroupDns() { assertFalse(target.isLimitlessDbShardGroupDns(usEastRegionCustomDomain)); assertFalse(target.isLimitlessDbShardGroupDns(usEastRegionElbUrl)); assertTrue(target.isLimitlessDbShardGroupDns(usEastRegionLimitlessDbShardGroup)); + assertFalse(target.isLimitlessDbShardGroupDns(euRedshift)); assertFalse(target.isLimitlessDbShardGroupDns(usIsobEastRegionCluster)); assertFalse(target.isLimitlessDbShardGroupDns(usIsobEastRegionClusterReadOnly)); @@ -412,6 +423,9 @@ public void testGetRdsRegion() { assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionProxy)); assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionCustomDomain)); assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionLimitlessDbShardGroup)); + + final String euRedshiftExpectedHostPattern = "eusc-de-east-1"; + assertEquals(euRedshiftExpectedHostPattern, target.getRdsRegion(euRedshift)); } @Test From da670816843f64827ab8531f82cf99bfaa4e8547 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 31 Oct 2025 14:15:28 -0700 Subject: [PATCH 21/46] RdsHostListProviderTest passing --- .../hostlistprovider/RdsHostListProvider.java | 2 +- .../RdsHostListProviderTest.java | 144 +----------------- 2 files changed, 7 insertions(+), 139 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 1adb1a6c4..ac48b6442 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -261,7 +261,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f } // fetch topology from the DB - final List hosts = this.topologyUtils.queryForTopology(conn); + final List hosts = this.queryForTopology(conn); if (!Utils.isNullOrEmpty(hosts)) { this.servicesContainer.getStorageService().set(this.clusterId, new Topology(hosts)); diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 9ee39e1b5..ceadddb74 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -146,7 +146,7 @@ void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLExceptio final List newHosts = Collections.singletonList( new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); - doReturn(newHosts).when(rdsHostListProvider).queryForTopology(mockConnection); + doReturn(newHosts).when(mockTopologyUtils).queryForTopology(mockConnection); final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); @@ -194,9 +194,7 @@ void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { final List expectedPostgres = Collections.singletonList( new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); - when(mockResultSet.next()).thenReturn(true, false); - when(mockResultSet.getBoolean(eq(2))).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("mysql"); + when(mockTopologyUtils.queryForTopology(mockConnection)).thenReturn(expectedMySQL).thenReturn(expectedPostgres); rdsHostListProvider = getRdsHostListProvider("mysql://url/"); @@ -204,24 +202,11 @@ void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { List hosts = rdsHostListProvider.queryForTopology(mockConnection); assertEquals(expectedMySQL, hosts); - when(mockResultSet.next()).thenReturn(true, false); - when(mockResultSet.getString(eq(1))).thenReturn("postgresql"); - rdsHostListProvider = getRdsHostListProvider("postgresql://url/"); hosts = rdsHostListProvider.queryForTopology(mockConnection); assertEquals(expectedPostgres, hosts); } - @Test - void testQueryForTopology_queryResultsInException() throws SQLException { - rdsHostListProvider = getRdsHostListProvider("protocol://url/"); - when(mockStatement.executeQuery(queryCaptor.capture())).thenThrow(new SQLSyntaxErrorException()); - - assertThrows( - SQLException.class, - () -> rdsHostListProvider.queryForTopology(mockConnection)); - } - @Test void testGetCachedTopology_returnStoredTopology() throws SQLException { rdsHostListProvider = getRdsHostListProvider("jdbc:someprotocol://url"); @@ -298,7 +283,7 @@ void testTopologyCache_SuggestedClusterIdForRds() throws SQLException { .role(HostRole.READER) .build()); - doReturn(topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); + doReturn(topologyClusterA).when(mockTopologyUtils).queryForTopology(any(Connection.class)); assertEquals(0, storageService.size(Topology.class)); @@ -441,8 +426,7 @@ void testIdentifyConnectionNullTopology() throws SQLException { rdsHostListProvider.clusterInstanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) .host("?.pattern").build(); - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); doReturn(null).when(rdsHostListProvider).refresh(mockConnection); doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); @@ -459,8 +443,7 @@ void testIdentifyConnectionHostNotInTopology() throws SQLException { .build()); rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); @@ -478,8 +461,7 @@ void testIdentifyConnectionHostInTopology() throws SQLException { final List cachedTopology = Collections.singletonList(expectedHost); rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockResultSet.next()).thenReturn(true); - when(mockResultSet.getString(eq(1))).thenReturn("instance-a-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); @@ -488,123 +470,9 @@ void testIdentifyConnectionHostInTopology() throws SQLException { assertEquals("instance-a-1", actual.getHostId()); } - @Test - void testGetTopology_StaleRecord() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.isInitialized = true; - - final String hostName1 = "hostName1"; - final String hostName2 = "hostName2"; - final Double cpuUtilization = 11.1D; - final Double nodeLag = 0.123D; - final Timestamp firstTimestamp = Timestamp.from(Instant.now()); - final Timestamp secondTimestamp = new Timestamp(firstTimestamp.getTime() + 100); - when(mockResultSet.next()).thenReturn(true, true, false); - when(mockResultSet.getString(1)).thenReturn(hostName1).thenReturn(hostName2); - when(mockResultSet.getBoolean(2)).thenReturn(true).thenReturn(true); - when(mockResultSet.getDouble(3)).thenReturn(cpuUtilization).thenReturn(cpuUtilization); - when(mockResultSet.getDouble(4)).thenReturn(nodeLag).thenReturn(nodeLag); - when(mockResultSet.getTimestamp(5)).thenReturn(firstTimestamp).thenReturn(secondTimestamp); - long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); - final HostSpec expectedWriter = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host(hostName2) - .port(-1) - .role(HostRole.WRITER) - .availability(HostAvailability.AVAILABLE) - .weight(weight) - .lastUpdateTime(secondTimestamp) - .build(); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertEquals(1, result.hosts.size()); - assertEquals(expectedWriter, result.hosts.get(0)); - } - - @Test - void testGetTopology_InvalidLastUpdatedTimestamp() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.isInitialized = true; - final String hostName = "hostName"; - final Double cpuUtilization = 11.1D; - final Double nodeLag = 0.123D; - when(mockResultSet.next()).thenReturn(true, false); - when(mockResultSet.getString(1)).thenReturn(hostName); - when(mockResultSet.getBoolean(2)).thenReturn(true); - when(mockResultSet.getDouble(3)).thenReturn(cpuUtilization); - when(mockResultSet.getDouble(4)).thenReturn(nodeLag); - when(mockResultSet.getTimestamp(5)).thenThrow(WrongArgumentException.class); - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - final String expectedLastUpdatedTimeStampRounded = Timestamp.from(Instant.now()).toString().substring(0, 16); - assertEquals(1, result.hosts.size()); - assertEquals( - expectedLastUpdatedTimeStampRounded, - result.hosts.get(0).getLastUpdateTime().toString().substring(0, 16)); - } - - @Test - void testGetTopology_returnsLatestWriter() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.isInitialized = true; - - HostSpec expectedWriterHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("expectedWriterHost") - .role(HostRole.WRITER) - .lastUpdateTime(Timestamp.valueOf("3000-01-01 00:00:00")) - .build(); - - HostSpec unexpectedWriterHost0 = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("unexpectedWriterHost0") - .role(HostRole.WRITER) - .lastUpdateTime(Timestamp.valueOf("1000-01-01 00:00:00")) - .build(); - - HostSpec unexpectedWriterHost1 = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("unexpectedWriterHost1") - .role(HostRole.WRITER) - .lastUpdateTime(Timestamp.valueOf("2000-01-01 00:00:00")) - .build(); - - HostSpec unexpectedWriterHostWithNullLastUpdateTime0 = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("unexpectedWriterHostWithNullLastUpdateTime0") - .role(HostRole.WRITER) - .lastUpdateTime(null) - .build(); - - HostSpec unexpectedWriterHostWithNullLastUpdateTime1 = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("unexpectedWriterHostWithNullLastUpdateTime1") - .role(HostRole.WRITER) - .lastUpdateTime(null) - .build(); - - when(mockResultSet.next()).thenReturn(true, true, true, true, true, false); - - when(mockResultSet.getString(1)).thenReturn( - unexpectedWriterHostWithNullLastUpdateTime0.getHost(), - unexpectedWriterHost0.getHost(), - expectedWriterHost.getHost(), - unexpectedWriterHost1.getHost(), - unexpectedWriterHostWithNullLastUpdateTime1.getHost()); - when(mockResultSet.getBoolean(2)).thenReturn(true, true, true, true, true); - when(mockResultSet.getFloat(3)).thenReturn((float) 0, (float) 0, (float) 0, (float) 0, (float) 0); - when(mockResultSet.getFloat(4)).thenReturn((float) 0, (float) 0, (float) 0, (float) 0, (float) 0); - when(mockResultSet.getTimestamp(5)).thenReturn( - unexpectedWriterHostWithNullLastUpdateTime0.getLastUpdateTime(), - unexpectedWriterHost0.getLastUpdateTime(), - expectedWriterHost.getLastUpdateTime(), - unexpectedWriterHost1.getLastUpdateTime(), - unexpectedWriterHostWithNullLastUpdateTime1.getLastUpdateTime() - ); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - - assertEquals(expectedWriterHost.getHost(), result.hosts.get(0).getHost()); - } @Test void testClusterUrlUsedAsDefaultClusterId() throws SQLException { From c6e19577eba896d996ae5982dd530066aa2a8ee5 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 31 Oct 2025 14:40:44 -0700 Subject: [PATCH 22/46] Unit tests passing --- .../monitoring/ClusterTopologyMonitorImpl.java | 2 +- .../java/software/amazon/jdbc/DialectDetectionTests.java | 4 +++- .../jdbc/hostlistprovider/RdsHostListProviderTest.java | 5 ----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index ccb74f843..a9048dc1a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -41,6 +41,7 @@ import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostlistprovider.Topology; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.ExecutorFactory; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; @@ -49,7 +50,6 @@ import software.amazon.jdbc.util.ServiceUtility; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.SynchronousExecutor; -import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.monitoring.AbstractMonitor; diff --git a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java index 49cde84b1..1a5517559 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/DialectDetectionTests.java @@ -71,10 +71,10 @@ public class DialectDetectionTests { @Mock private Statement mockStatement; @Mock private ResultSet mockSuccessResultSet; @Mock private ResultSet mockFailResultSet; + @Mock private ResultSetMetaData mockResultSetMetaData; @Mock private HostSpec mockHost; @Mock private ConnectionPluginManager mockPluginManager; @Mock private TargetDriverDialect mockTargetDriverDialect; - @Mock private ResultSetMetaData mockResultSetMetaData; @BeforeEach void setUp() throws SQLException { @@ -84,6 +84,8 @@ void setUp() throws SQLException { when(this.mockServicesContainer.getStorageService()).thenReturn(mockStorageService); when(this.mockConnection.createStatement()).thenReturn(this.mockStatement); when(this.mockHost.getUrl()).thenReturn("url"); + when(this.mockFailResultSet.getMetaData()).thenReturn(mockResultSetMetaData); + when(this.mockResultSetMetaData.getColumnCount()).thenReturn(4); when(this.mockFailResultSet.next()).thenReturn(false); mockPluginManager.plugins = new ArrayList<>(); } diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index ceadddb74..5634bcc37 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -73,8 +73,6 @@ class RdsHostListProviderTest { private RdsHostListProvider rdsHostListProvider; @Mock private Connection mockConnection; - @Mock private Statement mockStatement; - @Mock private ResultSet mockResultSet; @Mock private FullServicesContainer mockServicesContainer; @Mock private PluginService mockPluginService; @Mock private HostListProviderService mockHostListProviderService; @@ -102,8 +100,6 @@ void setUp() throws SQLException { when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); - when(mockConnection.createStatement()).thenReturn(mockStatement); - when(mockStatement.executeQuery(queryCaptor.capture())).thenReturn(mockResultSet); when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); when(mockHostListProviderService.getHostSpecBuilder()) .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); @@ -413,7 +409,6 @@ void testTopologyCache_AcceptSuggestion() throws SQLException { void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockResultSet.next()).thenReturn(false); assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); From bca62914180302647ce9538749cc27007f957d8b Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Fri, 31 Oct 2025 15:50:07 -0700 Subject: [PATCH 23/46] Fix integ tests --- .../amazon/jdbc/hostlistprovider/RdsHostListProvider.java | 3 +++ .../monitoring/MonitoringRdsHostListProvider.java | 5 ----- .../plugin/failover/ClusterAwareReaderFailoverHandler.java | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index ac48b6442..82a289840 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -353,6 +353,7 @@ protected void suggestPrimaryCluster(final @NonNull List primaryCluste * @throws SQLException if errors occurred while retrieving the topology. */ protected List queryForTopology(final Connection conn) throws SQLException { + init(); return this.topologyUtils.queryForTopology(conn); } @@ -466,11 +467,13 @@ public FetchTopologyResult(final boolean isCachedData, final List host @Override public HostRole getHostRole(Connection conn) throws SQLException { + init(); return this.topologyUtils.getHostRole(conn); } @Override public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { + init(); try { String instanceId = this.topologyUtils.getInstanceId(connection); if (instanceId == null) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index df15a2b80..1fda39cdb 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -68,11 +68,6 @@ public static void clearCache() { clearAll(); } - @Override - protected void init() throws SQLException { - super.init(); - } - protected ClusterTopologyMonitor initMonitor() throws SQLException { return this.servicesContainer.getMonitorService().runIfAbsent( ClusterTopologyMonitorImpl.class, diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareReaderFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareReaderFailoverHandler.java index 6b8661e02..1b5078d26 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareReaderFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareReaderFailoverHandler.java @@ -317,7 +317,7 @@ private ReaderFailoverResult getConnectionFromHostGroup(final List hos } } - return new ReaderFailoverResult(null, null, false); + return FAILED_READER_FAILOVER_RESULT; } finally { executor.shutdownNow(); } @@ -364,7 +364,7 @@ private ReaderFailoverResult getResultFromNextTaskBatch( return result; } } - return new ReaderFailoverResult(null, null, false); + return FAILED_READER_FAILOVER_RESULT; } private ReaderFailoverResult getNextResult(final CompletionService service) From b4a4e4b245f5a6846c8d98cd3956e93e3c95fb99 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Mon, 3 Nov 2025 09:40:36 -0800 Subject: [PATCH 24/46] Global endpoints wip --- .../jdbc/dialect/AuroraDialectUtils.java | 1 - .../jdbc/dialect/AuroraMysqlDialect.java | 14 +- .../amazon/jdbc/dialect/AuroraPgDialect.java | 12 +- .../dialect/GlobalAuroraDialectUtils.java | 43 ++++ .../dialect/GlobalAuroraMysqlDialect.java | 93 +++---- .../jdbc/dialect/GlobalAuroraPgDialect.java | 114 ++++----- .../jdbc/dialect/TopologyQueryHostSpec.java | 12 + .../AuroraGlobalDbHostListProvider.java | 38 +-- .../hostlistprovider/RdsHostListProvider.java | 9 +- .../jdbc/hostlistprovider/TopologyUtils.java | 108 ++++++-- ...oraGlobalDbMonitoringHostListProvider.java | 22 +- .../ClusterTopologyMonitorImpl.java | 38 +-- .../GlobalDbClusterTopologyMonitorImpl.java | 31 +-- .../RdsHostListProviderTest.java | 232 +----------------- 14 files changed, 260 insertions(+), 507 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java index 14d802eed..ee1f4d145 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java @@ -79,7 +79,6 @@ protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQL // Calculate weight based on instance lag in time and CPU utilization. final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); - return new TopologyQueryHostSpec(hostName, isWriter, weight, lastUpdateTime); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index b43129230..34eafc735 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -39,7 +39,7 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, + "WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID' "; protected static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; - protected static final String IS_WRITER_QUERY = + protected static final String WRITER_ID_QUERY = "SELECT SERVER_ID FROM information_schema.replica_host_status " + "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id"; protected static final String IS_READER_QUERY = "SELECT @@innodb_read_only"; @@ -49,7 +49,17 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, + " table_schema = 'mysql' AND table_name = 'rds_topology'"; protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; - protected final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(IS_WRITER_QUERY); + protected final AuroraDialectUtils dialectUtils; + + public AuroraMysqlDialect() { + super(); + this.dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); + } + + public AuroraMysqlDialect(AuroraDialectUtils dialectUtils) { + super(); + this.dialectUtils = dialectUtils; + } @Override public boolean isDialect(final Connection connection) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 7275c634c..5984de366 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -65,7 +65,17 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); - protected final AuroraDialectUtils dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); + protected final AuroraDialectUtils dialectUtils; + + public AuroraPgDialect() { + super(); + this.dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); + } + + public AuroraPgDialect(AuroraDialectUtils dialectUtils) { + super(); + this.dialectUtils = dialectUtils; + } @Override public boolean isDialect(final Connection connection) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java new file mode 100644 index 000000000..b5d6e1d8d --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java @@ -0,0 +1,43 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import software.amazon.jdbc.HostSpec; + +public class GlobalAuroraDialectUtils extends AuroraDialectUtils { + public GlobalAuroraDialectUtils(String writerIdQuery) { + super(writerIdQuery); + } + + @Override + protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { + // The topology query results should contain 4 columns: + // node ID, 1/0 (writer/reader), node lag in time (ms), AWS region. + String hostName = resultSet.getString(1); + final boolean isWriter = resultSet.getBoolean(2); + final float nodeLag = resultSet.getFloat(3); + final String region = resultSet.getString(4); + + // Calculate weight based on node lag in time and CPU utilization. + final long weight = Math.round(nodeLag) * 100L; + return new TopologyQueryHostSpec(hostName, isWriter, weight, Timestamp.from(Instant.now()), region); + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java index 3e4db74e6..73f0231a1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java @@ -29,72 +29,54 @@ public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect { - protected final String globalDbStatusTableExistQuery = + protected final String GLOBAL_STATUS_TABLE_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " upper(table_schema) = 'INFORMATION_SCHEMA' AND upper(table_name) = 'AURORA_GLOBAL_DB_STATUS'"; - - protected final String globalDbStatusQuery = - "SELECT count(1) FROM information_schema.aurora_global_db_status"; - - protected final String globalDbInstanceStatusTableExistQuery = + protected final String GLOBAL_INSTANCE_STATUS_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " upper(table_schema) = 'INFORMATION_SCHEMA' AND upper(table_name) = 'AURORA_GLOBAL_DB_INSTANCE_STATUS'"; - protected final String globalTopologyQuery = + protected final String GLOBAL_TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "VISIBILITY_LAG_IN_MSEC, AWS_REGION " + "FROM information_schema.aurora_global_db_instance_status "; - protected final String regionByNodeIdQuery = + protected final String REGION_COUNT_QUERY = "SELECT count(1) FROM information_schema.aurora_global_db_status"; + protected final String REGION_BY_INSTANCE_ID_QUERY = "SELECT AWS_REGION FROM information_schema.aurora_global_db_instance_status WHERE SERVER_ID = ?"; + public GlobalAuroraMysqlDialect() { + super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY)); + } + @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbStatusTableExistQuery); - - if (rs.next()) { - rs.close(); - stmt.close(); - - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbInstanceStatusTableExistQuery); - - if (rs.next()) { - rs.close(); - stmt.close(); - - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbStatusQuery); - - if (rs.next()) { - int awsRegionCount = rs.getInt(1); - return awsRegionCount > 1; - } + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(this.GLOBAL_STATUS_TABLE_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } } - return false; - } catch (final SQLException ex) { - // ignore - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(this.GLOBAL_INSTANCE_STATUS_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } } - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(this.REGION_COUNT_QUERY)) { + if (rs.next()) { + int awsRegionCount = rs.getInt(1); + return awsRegionCount > 1; } } + } catch (final SQLException ex) { + return false; } + return false; } @@ -104,27 +86,14 @@ public boolean isDialect(final Connection connection) { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new AuroraGlobalDbMonitoringHostListProvider( - properties, - initialUrl, - servicesContainer, - this.globalTopologyQuery, - this.nodeIdQuery, - this.isReaderQuery, - this.isWriterQuery, - this.regionByNodeIdQuery); + return new AuroraGlobalDbMonitoringHostListProvider(this, properties, initialUrl, servicesContainer); } - return new AuroraGlobalDbHostListProvider( - properties, - initialUrl, - servicesContainer, - this.globalTopologyQuery, - this.nodeIdQuery, - this.isReaderQuery); + + return new AuroraGlobalDbHostListProvider(this, properties, initialUrl, servicesContainer); }; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java index 289ced4ae..5971f8a40 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java @@ -30,84 +30,66 @@ public class GlobalAuroraPgDialect extends AuroraPgDialect { - private static final Logger LOGGER = Logger.getLogger(GlobalAuroraPgDialect.class.getName()); - - protected final String globalDbStatusFuncExistQuery = - "select 'aurora_global_db_status'::regproc"; - - protected final String globalDbInstanceStatusFuncExistQuery = + protected static final String GLOBAL_STATUS_FUNC_EXISTS_QUERY = "select 'aurora_global_db_status'::regproc"; + protected static final String GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY = "select 'aurora_global_db_instance_status'::regproc"; - protected final String globalTopologyQuery = + protected static final String GLOBAL_TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "VISIBILITY_LAG_IN_MSEC, AWS_REGION " + "FROM aurora_global_db_instance_status()"; - protected final String globalDbStatusQuery = - "SELECT count(1) FROM aurora_global_db_status()"; - - protected final String regionByNodeIdQuery = + protected static final String REGION_COUNT_QUERY = "SELECT count(1) FROM aurora_global_db_status()"; + protected static final String REGION_BY_INSTANCE_ID_QUERY = "SELECT AWS_REGION FROM aurora_global_db_instance_status() WHERE SERVER_ID = ?"; + private static final Logger LOGGER = Logger.getLogger(GlobalAuroraPgDialect.class.getName()); + + public GlobalAuroraPgDialect() { + super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY)); + } + @Override public boolean isDialect(final Connection connection) { - Statement stmt = null; - ResultSet rs = null; try { - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.extensionsSql); - if (rs.next()) { + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(AURORA_UTILS_EXIST_QUERY)) { + if (!rs.next()) { + return false; + } + final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); LOGGER.finest(() -> String.format("auroraUtils: %b", auroraUtils)); if (!auroraUtils) { return false; } } - rs.close(); - stmt.close(); - - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbStatusFuncExistQuery); - - if (rs.next()) { - rs.close(); - stmt.close(); - - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbInstanceStatusFuncExistQuery); - - if (rs.next()) { - rs.close(); - stmt.close(); - stmt = connection.createStatement(); - rs = stmt.executeQuery(this.globalDbStatusQuery); - - if (rs.next()) { - int awsRegionCount = rs.getInt(1); - return awsRegionCount > 1; - } + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(GLOBAL_STATUS_FUNC_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } } - return false; - } catch (final SQLException ex) { - // ignore - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException ex) { - // ignore + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } } - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException ex) { - // ignore + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { + if (rs.next()) { + int awsRegionCount = rs.getInt(1); + return awsRegionCount > 1; } } + } catch (final SQLException ex) { + return false; } + return false; } @@ -117,27 +99,19 @@ public boolean isDialect(final Connection connection) { } @Override - public HostListProviderSupplier getHostListProvider() { + public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new AuroraGlobalDbMonitoringHostListProvider( - properties, - initialUrl, - servicesContainer, - this.globalTopologyQuery, - this.nodeIdQuery, - this.isReaderQuery, - this.isWriterQuery, - this.regionByNodeIdQuery); + return new AuroraGlobalDbMonitoringHostListProvider(this, properties, initialUrl, servicesContainer); } - return new AuroraGlobalDbHostListProvider( - properties, - initialUrl, - servicesContainer, - this.globalTopologyQuery, - this.nodeIdQuery, - this.isReaderQuery); + + return new AuroraGlobalDbHostListProvider(this, properties, initialUrl, servicesContainer); }; } + + @Override + public List processTopologyResults(Connection conn, ResultSet rs) { + + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java index 4ee19c2c4..b4cb7a2c3 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java @@ -17,18 +17,26 @@ package software.amazon.jdbc.dialect; import java.sql.Timestamp; +import org.checkerframework.checker.nullness.qual.Nullable; public class TopologyQueryHostSpec { private final String instanceId; private final boolean isWriter; private final long weight; private final Timestamp lastUpdateTime; + private final @Nullable String region; public TopologyQueryHostSpec(String instanceId, boolean isWriter, long weight, Timestamp lastUpdateTime) { + this(instanceId, isWriter, weight, lastUpdateTime, null); + } + + public TopologyQueryHostSpec( + String instanceId, boolean isWriter, long weight, Timestamp lastUpdateTime, @Nullable String region) { this.instanceId = instanceId; this.isWriter = isWriter; this.weight = weight; this.lastUpdateTime = lastUpdateTime; + this.region = region; } public String getInstanceId() { @@ -46,4 +54,8 @@ public long getWeight() { public Timestamp getLastUpdateTime() { return lastUpdateTime; } + + public @Nullable String getRegion() { + return this.region; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java index d85069323..e8c0fae65 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java @@ -26,16 +26,17 @@ import java.util.logging.Logger; import java.util.stream.Collectors; import software.amazon.jdbc.AwsWrapperProperty; -import software.amazon.jdbc.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class AuroraGlobalDbHostListProvider extends AuroraHostListProvider { +public class AuroraGlobalDbHostListProvider extends RdsHostListProvider { static final Logger LOGGER = Logger.getLogger(AuroraGlobalDbHostListProvider.class.getName()); @@ -57,10 +58,9 @@ public class AuroraGlobalDbHostListProvider extends AuroraHostListProvider { PropertyDefinition.registerPluginProperties(AuroraGlobalDbHostListProvider.class); } - public AuroraGlobalDbHostListProvider(Properties properties, String originalUrl, - final FullServicesContainer servicesContainer, String topologyQuery, - String nodeIdQuery, String isReaderQuery) { - super(properties, originalUrl, servicesContainer, topologyQuery, nodeIdQuery, isReaderQuery); + public AuroraGlobalDbHostListProvider( + TopologyDialect dialect, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { + super(dialect, properties, originalUrl, servicesContainer); } @Override @@ -76,7 +76,7 @@ protected void initSettings() throws SQLException { this.globalClusterInstanceTemplateByAwsRegion = Arrays.stream(templates.split(",")) .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) .collect(Collectors.toMap( - k -> k.getValue1(), + Pair::getValue1, v -> { this.validateHostPatternSetting(v.getValue2().getHost()); return v.getValue2(); @@ -87,28 +87,4 @@ protected void initSettings() throws SQLException { .collect(Collectors.joining("\n")) ); } - - @Override - protected HostSpec createHost(final ResultSet resultSet) throws SQLException { - - // suggestedWriterNodeId is not used for Aurora Global Database clusters. - // Topology query can detect a writer for itself. - - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), node lag in time (msec), AWS region. - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final float nodeLag = resultSet.getFloat(3); - final String awsRegion = resultSet.getString(4); - - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L; - - final HostSpec clusterInstanceTemplateForRegion = this.globalClusterInstanceTemplateByAwsRegion.get(awsRegion); - if (clusterInstanceTemplateForRegion == null) { - throw new SQLException("Can't find cluster template for region " + awsRegion); - } - - return createHost(hostName, isWriter, weight, Timestamp.from(Instant.now()), clusterInstanceTemplateForRegion); - } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index cd8558c91..7cd85fe1c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -21,15 +21,11 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; import java.util.Objects; import java.util.Properties; -import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.AwsWrapperProperty; import software.amazon.jdbc.HostRole; @@ -42,9 +38,7 @@ import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; -import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.Utils; -import software.amazon.jdbc.util.storage.CacheMap; public class RdsHostListProvider implements DynamicHostListProvider { @@ -179,7 +173,6 @@ protected void initSettings() throws SQLException { if (this.topologyUtils == null) { this.topologyUtils = new TopologyUtils( this.dialect, - this.clusterInstanceTemplate, this.initialHostSpec, this.servicesContainer.getPluginService().getHostSpecBuilder()); } @@ -238,7 +231,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f */ protected List queryForTopology(final Connection conn) throws SQLException { init(); - return this.topologyUtils.queryForTopology(conn); + return this.topologyUtils.queryForTopology(conn, this.clusterInstanceTemplate); } /** diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index 542783f5c..95880e1af 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -36,7 +37,9 @@ import software.amazon.jdbc.dialect.TopologyQueryHostSpec; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.SynchronousExecutor; +import software.amazon.jdbc.util.Utils; public class TopologyUtils { private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); @@ -44,38 +47,67 @@ public class TopologyUtils { protected final Executor networkTimeoutExecutor = new SynchronousExecutor(); protected final TopologyDialect dialect; - protected final HostSpec clusterInstanceTemplate; protected final HostSpec initialHostSpec; protected final HostSpecBuilder hostSpecBuilder; public TopologyUtils( TopologyDialect dialect, - HostSpec clusterInstanceTemplate, HostSpec initialHostSpec, HostSpecBuilder hostSpecBuilder) { this.dialect = dialect; - this.clusterInstanceTemplate = clusterInstanceTemplate; this.initialHostSpec = initialHostSpec; this.hostSpecBuilder = hostSpecBuilder; } - public @Nullable List queryForTopology(Connection conn) throws SQLException { - int networkTimeout = -1; - try { - networkTimeout = conn.getNetworkTimeout(); - // The topology query is not monitored by the EFM plugin, so it needs a socket timeout - if (networkTimeout == 0) { - conn.setNetworkTimeout(this.networkTimeoutExecutor, DEFAULT_QUERY_TIMEOUT_MS); + public @Nullable List queryForTopology(Connection conn, HostSpec hostTemplate) throws SQLException { + int networkTimeout = setNetworkTimeout(conn); + try (final Statement stmt = conn.createStatement(); + final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { + List hosts = new ArrayList<>(); + List queryHosts = this.dialect.processTopologyResults(conn, resultSet); + if (Utils.isNullOrEmpty(queryHosts)) { + return null; + } + + for (TopologyQueryHostSpec queryHost : queryHosts) { + hosts.add(this.toHostspec(queryHost, hostTemplate)); + } + + return this.verifyWriter(hosts); + } catch (final SQLSyntaxErrorException e) { + throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); + } finally { + if (networkTimeout == 0 && !conn.isClosed()) { + conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); } - } catch (SQLException e) { - LOGGER.warning(() -> Messages.get("TopologyUtils.errorGettingNetworkTimeout", - new Object[] {e.getMessage()})); } + } + public @Nullable List queryForTopology(Connection conn, Map hostTemplateByRegion) throws SQLException { + int networkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { + List hosts = new ArrayList<>(); List queryHosts = this.dialect.processTopologyResults(conn, resultSet); - return this.processTopologyResults(queryHosts); + if (Utils.isNullOrEmpty(queryHosts)) { + return null; + } + + for (TopologyQueryHostSpec queryHost : queryHosts) { + String region = queryHost.getRegion(); + if (region == null) { + throw new SQLException(""); + } + + HostSpec template = hostTemplateByRegion.get(region); + if (template == null) { + throw new SQLException(""); + } + + hosts.add(this.toHostspec(queryHost, template)); + } + + return this.verifyWriter(hosts); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -85,18 +117,29 @@ public TopologyUtils( } } - protected @Nullable List processTopologyResults(@Nullable List queryHosts) { - if (queryHosts == null) { - return null; + private int setNetworkTimeout(Connection conn) { + int networkTimeout = -1; + try { + networkTimeout = conn.getNetworkTimeout(); + // The topology query is not monitored by the EFM plugin, so it needs a socket timeout + if (networkTimeout == 0) { + conn.setNetworkTimeout(this.networkTimeoutExecutor, DEFAULT_QUERY_TIMEOUT_MS); + } + } catch (SQLException e) { + LOGGER.warning(() -> Messages.get("TopologyUtils.errorGettingNetworkTimeout", + new Object[] {e.getMessage()})); } + return networkTimeout; + } + protected @Nullable List verifyWriter(List allHosts) { List hosts = new ArrayList<>(); List writers = new ArrayList<>(); - for (TopologyQueryHostSpec queryHost : queryHosts) { - if (queryHost.isWriter()) { - writers.add(this.toHostspec(queryHost)); + for (HostSpec host : allHosts) { + if (HostRole.WRITER == host.getRole()) { + writers.add(host); } else { - hosts.add(this.toHostspec(queryHost)); + hosts.add(host); } } @@ -117,11 +160,11 @@ public TopologyUtils( return hosts; } - protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { + protected HostSpec toHostspec(TopologyQueryHostSpec queryHost, HostSpec hostTemplate) { final String instanceId = queryHost.getInstanceId() == null ? "?" : queryHost.getInstanceId(); - final String endpoint = this.clusterInstanceTemplate.getHost().replace("?", instanceId); - final int port = this.clusterInstanceTemplate.isPortSpecified() - ? this.clusterInstanceTemplate.getPort() + final String endpoint = hostTemplate.getHost().replace("?", instanceId); + final int port = hostTemplate.isPortSpecified() + ? hostTemplate.getPort() : this.initialHostSpec.getPort(); final HostSpec hostSpec = this.hostSpecBuilder @@ -137,12 +180,23 @@ protected HostSpec toHostspec(TopologyQueryHostSpec queryHost) { return hostSpec; } - public @Nullable String getInstanceId(final Connection connection) { + /** + * Identifies instances across different database types using instanceId and instanceName values. + * + *

Database types handle these identifiers differently: + * - Aurora: Uses the instance name as both instanceId and instanceName + * Example: "test-instance-1" for both values + * - RDS Cluster: Uses distinct values for instanceId and instanceName + * Example: + * instanceId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" + * instanceName: "test-multiaz-instance-1" + */ + public @Nullable Pair getInstanceId(final Connection connection) { try { try (final Statement stmt = connection.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { if (resultSet.next()) { - return resultSet.getString(1); + return Pair.create(resultSet.getString(1), resultSet.getString(2)); } } } catch (SQLException ex) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java index 50bb4263d..abebe01d9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java @@ -28,6 +28,7 @@ import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; +import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostlistprovider.AuroraGlobalDbHostListProvider; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; @@ -44,21 +45,18 @@ public class AuroraGlobalDbMonitoringHostListProvider extends MonitoringRdsHostL protected final RdsUtils rdsUtils = new RdsUtils(); - protected String regionByNodeIdQuery; - static { // Register property definition in AuroraGlobalDbHostListProvider class. It's not a mistake. PropertyDefinition.registerPluginProperties(AuroraGlobalDbHostListProvider.class); } - public AuroraGlobalDbMonitoringHostListProvider(Properties properties, String originalUrl, - final FullServicesContainer servicesContainer, String globalTopologyQuery, - String nodeIdQuery, String isReaderQuery, String writerTopologyQuery, - String regionByNodeIdQuery) { + public AuroraGlobalDbMonitoringHostListProvider( + TopologyDialect dialect, + Properties properties, + String originalUrl, + FullServicesContainer servicesContainer) { - super(properties, originalUrl, servicesContainer, globalTopologyQuery, nodeIdQuery, isReaderQuery, - writerTopologyQuery); - this.regionByNodeIdQuery = regionByNodeIdQuery; + super(dialect, properties, originalUrl, servicesContainer); } @Override @@ -101,11 +99,7 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.clusterInstanceTemplate, this.refreshRateNano, this.highRefreshRateNano, - this.topologyQuery, - this.writerTopologyQuery, - this.nodeIdQuery, - this.globalClusterInstanceTemplateByAwsRegion, - this.regionByNodeIdQuery)); + this.globalClusterInstanceTemplateByAwsRegion)); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 0101572ab..b5bac6132 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -49,7 +49,6 @@ import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.ServiceUtility; -import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.SynchronousExecutor; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.monitoring.AbstractMonitor; @@ -119,10 +118,6 @@ public ClusterTopologyMonitorImpl( this.refreshRateNano = refreshRateNano; this.highRefreshRateNano = highRefreshRateNano; - this.initSettings(); - } - - protected void initSettings() { this.monitoringProperties = PropertyUtils.copyProperties(properties); this.properties.stringPropertyNames().stream() .filter(p -> p.startsWith(MONITORING_PROPERTY_PREFIX)) @@ -574,31 +569,6 @@ protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connecti return this.clusterInstanceTemplate; } - /** - * Identifies nodes across different database types using nodeId and nodeName values. - * - *

Database types handle these identifiers differently: - * - Aurora: Uses the instance (node) name as both nodeId and nodeName - * Example: "test-instance-1" for both values - * - RDS Cluster: Uses distinct values for nodeId and nodeName - * Example: - * nodeId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" - * nodeName: "test-multiaz-instance-1" - */ - protected Pair getNodeId(final Connection connection) { - try { - try (final Statement stmt = connection.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.nodeIdQuery)) { - if (resultSet.next()) { - return Pair.create(resultSet.getString(1), resultSet.getString(2)); - } - } - } catch (SQLException ex) { - // do nothing - } - return null; - } - protected void closeConnection(final @Nullable Connection connection) { try { if (connection != null && !connection.isClosed()) { @@ -638,7 +608,7 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { return null; } try { - final List hosts = this.topologyUtils.queryForTopology(connection); + final List hosts = this.queryForTopology(connection); if (!Utils.isNullOrEmpty(hosts)) { this.updateTopologyCache(hosts); } @@ -649,6 +619,10 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { return null; } + protected List queryForTopology(Connection connection) throws SQLException { + return this.topologyUtils.queryForTopology(connection, this.clusterInstanceTemplate); + } + protected void updateTopologyCache(final @NonNull List hosts) { synchronized (this.requestToUpdateTopology) { this.servicesContainer.getStorageService().set(this.clusterId, new Topology(hosts)); @@ -822,7 +796,7 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla List hosts; try { - hosts = this.monitor.topologyUtils.queryForTopology(connection); + hosts = this.monitor.topologyUtils.queryForTopology(connection, this.monitor.clusterInstanceTemplate); if (hosts == null) { return; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java index 0c2ee29a2..bb5c48309 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java @@ -58,10 +58,10 @@ public GlobalDbClusterTopologyMonitorImpl( } @Override - protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connection) { + protected HostSpec getClusterInstanceTemplate(String instanceId, Connection connection) { try { try (final PreparedStatement stmt = connection.prepareStatement(this.regionByNodeIdQuery)) { - stmt.setString(1, nodeId); + stmt.setString(1, instanceId); try (final ResultSet resultSet = stmt.executeQuery()) { if (resultSet.next()) { String awsRegion = resultSet.getString(1); @@ -81,31 +81,4 @@ protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connecti } return this.clusterInstanceTemplate; } - - @Override - protected HostSpec createHost( - final ResultSet resultSet, - final String suggestedWriterNodeId) throws SQLException { - - // suggestedWriterNodeId is not used for Aurora Global Database clusters. - // Topology query can detect a writer for itself. - - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), node lag in time (msec), AWS region. - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final float nodeLag = resultSet.getFloat(3); - final String awsRegion = resultSet.getString(4); - - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L; - - final HostSpec clusterInstanceTemplateForRegion = this.globalClusterInstanceTemplateByAwsRegion.get(awsRegion); - if (clusterInstanceTemplateForRegion == null) { - throw new SQLException("Can't find cluster template for region " + awsRegion); - } - - return createHost( - hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), clusterInstanceTemplateForRegion); - } } diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 5634bcc37..43ed89c53 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -17,31 +17,20 @@ package software.amazon.jdbc.hostlistprovider; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atMostOnce; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import com.mysql.cj.exceptions.WrongArgumentException; import java.sql.Connection; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLSyntaxErrorException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -108,7 +97,6 @@ void setUp() throws SQLException { @AfterEach void tearDown() throws Exception { - RdsHostListProvider.clearAll(); storageService.clearAll(); closeable.close(); } @@ -142,7 +130,7 @@ void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLExceptio final List newHosts = Collections.singletonList( new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); - doReturn(newHosts).when(mockTopologyUtils).queryForTopology(mockConnection); + doReturn(newHosts).when(mockTopologyUtils).queryForTopology(eq(mockConnection), any(HostSpec.class)); final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); @@ -190,7 +178,7 @@ void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { final List expectedPostgres = Collections.singletonList( new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); - when(mockTopologyUtils.queryForTopology(mockConnection)).thenReturn(expectedMySQL).thenReturn(expectedPostgres); + when(mockTopologyUtils.queryForTopology(eq(mockConnection), any(HostSpec.class))).thenReturn(expectedMySQL).thenReturn(expectedPostgres); rdsHostListProvider = getRdsHostListProvider("mysql://url/"); @@ -214,197 +202,6 @@ void testGetCachedTopology_returnStoredTopology() throws SQLException { assertEquals(expected, result); } - @Test - void testTopologyCache_NoSuggestedClusterId() throws SQLException { - RdsHostListProvider.clearAll(); - - RdsHostListProvider provider1 = Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-a.domain.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.domain.com").port(HostSpec.NO_PORT).role(HostRole.WRITER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build()); - - doReturn(topologyClusterA) - .when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsHostListProvider provider2 = Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-b.domain.com/")); - provider2.init(); - assertNull(provider2.getStoredTopology()); - - final List topologyClusterB = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-1.domain.com").port(HostSpec.NO_PORT).role(HostRole.WRITER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-2.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-b-3.domain.com").port(HostSpec.NO_PORT).role(HostRole.READER).build()); - doReturn(topologyClusterB).when(provider2).queryForTopology(any(Connection.class)); - - final List topologyProvider2 = provider2.refresh(mock(Connection.class)); - assertEquals(topologyClusterB, topologyProvider2); - - assertEquals(2, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_SuggestedClusterIdForRds() throws SQLException { - RdsHostListProvider.clearAll(); - - RdsHostListProvider provider1 = - Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doReturn(topologyClusterA).when(mockTopologyUtils).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsHostListProvider provider2 = - Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - final List topologyProvider2 = provider2.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertEquals(1, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_SuggestedClusterIdForInstance() throws SQLException { - RdsHostListProvider.clearAll(); - - RdsHostListProvider provider1 = - Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doReturn(topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - final List topologyProvider1 = provider1.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - RdsHostListProvider provider2 = - Mockito.spy(getRdsHostListProvider("jdbc:something://instance-a-3.xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - final List topologyProvider2 = provider2.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertEquals(1, storageService.size(Topology.class)); - } - - @Test - void testTopologyCache_AcceptSuggestion() throws SQLException { - RdsHostListProvider.clearAll(); - - RdsHostListProvider provider1 = - Mockito.spy(getRdsHostListProvider("jdbc:something://instance-a-2.xyz.us-east-2.rds.amazonaws.com/")); - provider1.init(); - final List topologyClusterA = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-2.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-3.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.READER) - .build()); - - doAnswer(a -> topologyClusterA).when(provider1).queryForTopology(any(Connection.class)); - - assertEquals(0, storageService.size(Topology.class)); - - List topologyProvider1 = provider1.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - - // RdsHostListProvider.logCache(); - - RdsHostListProvider provider2 = - Mockito.spy(getRdsHostListProvider("jdbc:something://cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com/")); - provider2.init(); - - doAnswer(a -> topologyClusterA).when(provider2).queryForTopology(any(Connection.class)); - - final List topologyProvider2 = provider2.refresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider2); - - assertNotEquals(provider1.clusterId, provider2.clusterId); - assertFalse(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - assertEquals(2, storageService.size(Topology.class)); - assertEquals("cluster-a.cluster-xyz.us-east-2.rds.amazonaws.com", - RdsHostListProvider.suggestedPrimaryClusterIdCache.get(provider1.clusterId)); - - // RdsHostListProvider.logCache(); - - topologyProvider1 = provider1.forceRefresh(mock(Connection.class)); - assertEquals(topologyClusterA, topologyProvider1); - assertEquals(provider1.clusterId, provider2.clusterId); - assertTrue(provider1.isPrimaryClusterId); - assertTrue(provider2.isPrimaryClusterId); - - // RdsHostListProvider.logCache(); - } - @Test void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); @@ -464,29 +261,4 @@ void testIdentifyConnectionHostInTopology() throws SQLException { assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); assertEquals("instance-a-1", actual.getHostId()); } - - - - - - @Test - void testClusterUrlUsedAsDefaultClusterId() throws SQLException { - String readerClusterUrl = "mycluster.cluster-ro-XYZ.us-east-1.rds.amazonaws.com"; - String expectedClusterId = "mycluster.cluster-XYZ.us-east-1.rds.amazonaws.com:1234"; - String connectionString = "jdbc:someprotocol://" + readerClusterUrl + ":1234/test"; - RdsHostListProvider provider1 = Mockito.spy(getRdsHostListProvider(connectionString)); - assertEquals(expectedClusterId, provider1.getClusterId()); - - List mockTopology = - Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host").build()); - doReturn(mockTopology).when(provider1).queryForTopology(any(Connection.class)); - provider1.refresh(); - assertEquals(mockTopology, provider1.getStoredTopology()); - verify(provider1, times(1)).queryForTopology(mockConnection); - - RdsHostListProvider provider2 = Mockito.spy(getRdsHostListProvider(connectionString)); - assertEquals(expectedClusterId, provider2.getClusterId()); - assertEquals(mockTopology, provider2.getStoredTopology()); - verify(provider2, never()).queryForTopology(mockConnection); - } } From 6c7071d5d6837be705a710d7bfcead620086eab9 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 4 Nov 2025 09:46:10 -0800 Subject: [PATCH 25/46] build passing --- .../dialect/GlobalAuroraDialectUtils.java | 32 +- .../dialect/GlobalAuroraMysqlDialect.java | 25 +- .../jdbc/dialect/GlobalAuroraPgDialect.java | 12 +- .../jdbc/dialect/GlobalTopologyDialect.java | 24 + .../hostlistprovider/RdsHostListProvider.java | 10 +- ...oraGlobalDbMonitoringHostListProvider.java | 14 +- .../ClusterTopologyMonitorImpl.java | 9 +- .../GlobalDbClusterTopologyMonitorImpl.java | 84 --- .../monitoring/GlobalTopologyMonitor.java | 76 +++ .../RdsHostListProviderTest.java | 528 +++++++++--------- 10 files changed, 436 insertions(+), 378 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java index b5d6e1d8d..294a614ac 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java @@ -16,28 +16,48 @@ package software.amazon.jdbc.dialect; +import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.time.Instant; -import software.amazon.jdbc.HostSpec; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.util.StringUtils; public class GlobalAuroraDialectUtils extends AuroraDialectUtils { - public GlobalAuroraDialectUtils(String writerIdQuery) { + protected final String regionByInstanceIdQuery; + + public GlobalAuroraDialectUtils(String writerIdQuery, String regionByInstanceIdQuery) { super(writerIdQuery); + this.regionByInstanceIdQuery = regionByInstanceIdQuery; } @Override protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { // The topology query results should contain 4 columns: - // node ID, 1/0 (writer/reader), node lag in time (ms), AWS region. + // instance ID, 1/0 (writer/reader), instance lag in time (ms), AWS region. String hostName = resultSet.getString(1); final boolean isWriter = resultSet.getBoolean(2); - final float nodeLag = resultSet.getFloat(3); + final float instanceLag = resultSet.getFloat(3); final String region = resultSet.getString(4); - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L; + // Calculate weight based on instance lag in time and CPU utilization. + final long weight = Math.round(instanceLag) * 100L; return new TopologyQueryHostSpec(hostName, isWriter, weight, Timestamp.from(Instant.now()), region); } + + protected @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { + try (final PreparedStatement stmt = conn.prepareStatement(this.regionByInstanceIdQuery)) { + stmt.setString(1, instanceId); + try (final ResultSet resultSet = stmt.executeQuery()) { + if (resultSet.next()) { + String awsRegion = resultSet.getString(1); + return StringUtils.isNullOrEmpty(awsRegion) ? null : awsRegion; + } + } + } + + return null; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java index 73f0231a1..8ae3b33c1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java @@ -27,26 +27,26 @@ import software.amazon.jdbc.hostlistprovider.monitoring.AuroraGlobalDbMonitoringHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; -public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect { +public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect implements GlobalTopologyDialect { - protected final String GLOBAL_STATUS_TABLE_EXISTS_QUERY = + protected static final String GLOBAL_STATUS_TABLE_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " upper(table_schema) = 'INFORMATION_SCHEMA' AND upper(table_name) = 'AURORA_GLOBAL_DB_STATUS'"; - protected final String GLOBAL_INSTANCE_STATUS_EXISTS_QUERY = + protected static final String GLOBAL_INSTANCE_STATUS_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " upper(table_schema) = 'INFORMATION_SCHEMA' AND upper(table_name) = 'AURORA_GLOBAL_DB_INSTANCE_STATUS'"; - protected final String GLOBAL_TOPOLOGY_QUERY = + protected static final String GLOBAL_TOPOLOGY_QUERY = "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "VISIBILITY_LAG_IN_MSEC, AWS_REGION " + "FROM information_schema.aurora_global_db_instance_status "; - protected final String REGION_COUNT_QUERY = "SELECT count(1) FROM information_schema.aurora_global_db_status"; - protected final String REGION_BY_INSTANCE_ID_QUERY = + protected static final String REGION_COUNT_QUERY = "SELECT count(1) FROM information_schema.aurora_global_db_status"; + protected static final String REGION_BY_INSTANCE_ID_QUERY = "SELECT AWS_REGION FROM information_schema.aurora_global_db_instance_status WHERE SERVER_ID = ?"; public GlobalAuroraMysqlDialect() { - super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY)); + super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY, REGION_BY_INSTANCE_ID_QUERY)); } @Override @@ -96,4 +96,15 @@ public HostListProviderSupplier getHostListProviderSupplier() { return new AuroraGlobalDbHostListProvider(this, properties, initialUrl, servicesContainer); }; } + + @Override + public String getRegion(String instanceId, Connection conn) + throws SQLException { + if (!(this.dialectUtils instanceof GlobalAuroraDialectUtils)) { + throw new SQLException(""); + } + + GlobalAuroraDialectUtils globalUtils = (GlobalAuroraDialectUtils) this.dialectUtils; + return globalUtils.getRegion(instanceId, conn); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java index 5971f8a40..0956f40b0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java @@ -28,7 +28,7 @@ import software.amazon.jdbc.hostlistprovider.monitoring.AuroraGlobalDbMonitoringHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; -public class GlobalAuroraPgDialect extends AuroraPgDialect { +public class GlobalAuroraPgDialect extends AuroraPgDialect implements GlobalTopologyDialect { protected static final String GLOBAL_STATUS_FUNC_EXISTS_QUERY = "select 'aurora_global_db_status'::regproc"; protected static final String GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY = @@ -46,7 +46,7 @@ public class GlobalAuroraPgDialect extends AuroraPgDialect { private static final Logger LOGGER = Logger.getLogger(GlobalAuroraPgDialect.class.getName()); public GlobalAuroraPgDialect() { - super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY)); + super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY, REGION_BY_INSTANCE_ID_QUERY)); } @Override @@ -111,7 +111,13 @@ public HostListProviderSupplier getHostListProviderSupplier() { } @Override - public List processTopologyResults(Connection conn, ResultSet rs) { + public String getRegion(String instanceId, Connection conn) + throws SQLException { + if (!(this.dialectUtils instanceof GlobalAuroraDialectUtils)) { + throw new SQLException(""); + } + GlobalAuroraDialectUtils globalUtils = (GlobalAuroraDialectUtils) this.dialectUtils; + return globalUtils.getRegion(instanceId, conn); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java new file mode 100644 index 000000000..c41c62ce5 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java @@ -0,0 +1,24 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Connection; +import java.sql.SQLException; + +public interface GlobalTopologyDialect extends TopologyDialect { + String getRegion(String instanceId, Connection conn) throws SQLException; +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 7cd85fe1c..a1e6ab18c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -36,6 +36,7 @@ import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.Utils; @@ -344,8 +345,8 @@ public HostRole getHostRole(Connection conn) throws SQLException { public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { init(); try { - String instanceId = this.topologyUtils.getInstanceId(connection); - if (instanceId == null) { + Pair instanceIds = this.topologyUtils.getInstanceId(connection); + if (instanceIds == null) { throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection")); } @@ -361,9 +362,10 @@ public HostRole getHostRole(Connection conn) throws SQLException { return null; } + String instanceName = instanceIds.getValue2(); HostSpec foundHost = topology .stream() - .filter(host -> Objects.equals(instanceId, host.getHostId())) + .filter(host -> Objects.equals(instanceName, host.getHostId())) .findAny() .orElse(null); @@ -375,7 +377,7 @@ public HostRole getHostRole(Connection conn) throws SQLException { foundHost = topology .stream() - .filter(host -> Objects.equals(instanceId, host.getHostId())) + .filter(host -> Objects.equals(instanceName, host.getHostId())) .findAny() .orElse(null); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java index abebe01d9..9fa906664 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java @@ -23,19 +23,16 @@ import java.util.Properties; import java.util.logging.Logger; import java.util.stream.Collectors; -import software.amazon.jdbc.HostListProviderService; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; -import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.dialect.GlobalTopologyDialect; import software.amazon.jdbc.hostlistprovider.AuroraGlobalDbHostListProvider; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -import software.amazon.jdbc.util.connection.ConnectionService; public class AuroraGlobalDbMonitoringHostListProvider extends MonitoringRdsHostListProvider { @@ -44,6 +41,7 @@ public class AuroraGlobalDbMonitoringHostListProvider extends MonitoringRdsHostL protected Map globalClusterInstanceTemplateByAwsRegion = new HashMap<>(); protected final RdsUtils rdsUtils = new RdsUtils(); + protected final GlobalTopologyDialect globalDialect; static { // Register property definition in AuroraGlobalDbHostListProvider class. It's not a mistake. @@ -51,12 +49,12 @@ public class AuroraGlobalDbMonitoringHostListProvider extends MonitoringRdsHostL } public AuroraGlobalDbMonitoringHostListProvider( - TopologyDialect dialect, + GlobalTopologyDialect dialect, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); + this.globalDialect = dialect; } @Override @@ -91,8 +89,10 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.servicesContainer, this.properties, (servicesContainer) -> - new GlobalDbClusterTopologyMonitorImpl( + new GlobalTopologyMonitor( servicesContainer, + this.topologyUtils, + this.globalDialect, this.clusterId, this.initialHostSpec, this.properties, diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index b5bac6132..d86c6bf90 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -523,8 +523,11 @@ protected List openAnyConnectionAndUpdateTopology() { } else { final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); if (pair != null) { - this.writerHostSpec.set(this.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, - this.getClusterInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()))); + HostSpec hostTemplate = + this.getClusterInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); + HostSpec writerHost = + this.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, hostTemplate); + this.writerHostSpec.set(writerHost); LOGGER.finest( Messages.get( "ClusterTopologyMonitorImpl.writerMonitoringConnection", @@ -565,7 +568,7 @@ protected List openAnyConnectionAndUpdateTopology() { return hosts; } - protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connection) { + protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connection) throws SQLException { return this.clusterInstanceTemplate; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java deleted file mode 100644 index bb5c48309..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalDbClusterTopologyMonitorImpl.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider.monitoring; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.Map; -import java.util.Properties; -import java.util.logging.Logger; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.StringUtils; - - -public class GlobalDbClusterTopologyMonitorImpl extends ClusterTopologyMonitorImpl { - - private static final Logger LOGGER = Logger.getLogger(GlobalDbClusterTopologyMonitorImpl.class.getName()); - - protected final Map globalClusterInstanceTemplateByAwsRegion; - protected final String regionByNodeIdQuery; - - public GlobalDbClusterTopologyMonitorImpl( - final FullServicesContainer servicesContainer, - final String clusterId, - final HostSpec initialHostSpec, - final Properties properties, - final HostSpec clusterInstanceTemplate, - final long refreshRateNano, - final long highRefreshRateNano, - final String topologyQuery, - final String writerTopologyQuery, - final String nodeIdQuery, - final Map globalClusterInstanceTemplateByAwsRegion, - final String regionByNodeIdQuery) { - - super(servicesContainer, clusterId, initialHostSpec, properties, clusterInstanceTemplate, - refreshRateNano, highRefreshRateNano, topologyQuery, writerTopologyQuery, nodeIdQuery); - this.globalClusterInstanceTemplateByAwsRegion = globalClusterInstanceTemplateByAwsRegion; - this.regionByNodeIdQuery = regionByNodeIdQuery; - } - - @Override - protected HostSpec getClusterInstanceTemplate(String instanceId, Connection connection) { - try { - try (final PreparedStatement stmt = connection.prepareStatement(this.regionByNodeIdQuery)) { - stmt.setString(1, instanceId); - try (final ResultSet resultSet = stmt.executeQuery()) { - if (resultSet.next()) { - String awsRegion = resultSet.getString(1); - if (!StringUtils.isNullOrEmpty(awsRegion)) { - final HostSpec clusterInstanceTemplateForRegion - = this.globalClusterInstanceTemplateByAwsRegion.get(awsRegion); - if (clusterInstanceTemplateForRegion == null) { - throw new SQLException("Can't find cluster template for region " + awsRegion); - } - return clusterInstanceTemplateForRegion; - } - } - } - } - } catch (SQLException ex) { - throw new RuntimeException(ex); - } - return this.clusterInstanceTemplate; - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java new file mode 100644 index 000000000..8bca82a44 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java @@ -0,0 +1,76 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.hostlistprovider.monitoring; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; +import java.util.Properties; +import java.util.logging.Logger; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.dialect.GlobalTopologyDialect; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; +import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.StringUtils; + + +public class GlobalTopologyMonitor extends ClusterTopologyMonitorImpl { + + private static final Logger LOGGER = Logger.getLogger(GlobalTopologyMonitor.class.getName()); + + protected final Map hostTemplatesByRegion; + protected final GlobalTopologyDialect dialect; + + public GlobalTopologyMonitor( + final FullServicesContainer servicesContainer, + final TopologyUtils topologyUtils, + final GlobalTopologyDialect dialect, + final String clusterId, + final HostSpec initialHostSpec, + final Properties properties, + final HostSpec clusterInstanceTemplate, + final long refreshRateNano, + final long highRefreshRateNano, + final Map hostTemplatesByRegion) { + super(servicesContainer, + topologyUtils, + clusterId, + initialHostSpec, + properties, + clusterInstanceTemplate, + refreshRateNano, + highRefreshRateNano); + + this.hostTemplatesByRegion = hostTemplatesByRegion; + this.dialect = dialect; + } + + @Override + protected HostSpec getClusterInstanceTemplate(String instanceId, Connection connection) throws SQLException { + String region = dialect.getRegion(instanceId, connection); + if (!StringUtils.isNullOrEmpty(region)) { + final HostSpec clusterInstanceTemplateForRegion = this.hostTemplatesByRegion.get(region); + if (clusterInstanceTemplateForRegion == null) { + throw new SQLException("Can't find cluster template for region " + region); + } + + return clusterInstanceTemplateForRegion; + } + + return this.clusterInstanceTemplate; + } +} diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 43ed89c53..29e0b55ca 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -1,264 +1,264 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.hostlistprovider; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.atMostOnce; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Properties; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import software.amazon.jdbc.HostRole; -import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.HostSpecBuilder; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.hostavailability.HostAvailability; -import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; -import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.events.EventPublisher; -import software.amazon.jdbc.util.storage.StorageService; -import software.amazon.jdbc.util.storage.TestStorageServiceImpl; - -class RdsHostListProviderTest { - private StorageService storageService; - private RdsHostListProvider rdsHostListProvider; - - @Mock private Connection mockConnection; - @Mock private FullServicesContainer mockServicesContainer; - @Mock private PluginService mockPluginService; - @Mock private HostListProviderService mockHostListProviderService; - @Mock private HostSpecBuilder mockHostSpecBuilder; - @Mock private EventPublisher mockEventPublisher; - @Mock private TopologyUtils mockTopologyUtils; - @Mock private TopologyDialect mockDialect; - @Captor private ArgumentCaptor queryCaptor; - - private AutoCloseable closeable; - private final HostSpec currentHostSpec = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("foo").port(1234).build(); - private final List hosts = Arrays.asList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host1").build(), - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host2").build()); - - @BeforeEach - void setUp() throws SQLException { - closeable = MockitoAnnotations.openMocks(this); - storageService = new TestStorageServiceImpl(mockEventPublisher); - when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); - when(mockServicesContainer.getStorageService()).thenReturn(storageService); - when(mockServicesContainer.getPluginService()).thenReturn(mockPluginService); - when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); - when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); - when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); - when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); - when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); - when(mockHostListProviderService.getHostSpecBuilder()) - .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); - when(mockHostListProviderService.getCurrentConnection()).thenReturn(mockConnection); - } - - @AfterEach - void tearDown() throws Exception { - storageService.clearAll(); - closeable.close(); - } - - private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { - RdsHostListProvider provider = new RdsHostListProvider( - mockDialect, new Properties(), originalUrl, mockServicesContainer, mockTopologyUtils); - provider.init(); - return provider; - } - - @Test - void testGetTopology_returnCachedTopology() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("protocol://url/")); - - final List expected = hosts; - storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); - assertEquals(expected, result.hosts); - assertEquals(2, result.hosts.size()); - verify(rdsHostListProvider, never()).queryForTopology(mockConnection); - } - - @Test - void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.isInitialized = true; - - storageService.set(rdsHostListProvider.clusterId, new Topology(hosts)); - - final List newHosts = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); - doReturn(newHosts).when(mockTopologyUtils).queryForTopology(eq(mockConnection), any(HostSpec.class)); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertEquals(1, result.hosts.size()); - assertEquals(newHosts, result.hosts); - } - - @Test - void testGetTopology_noForceUpdate_queryReturnsEmptyHostList() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.clusterId = "cluster-id"; - rdsHostListProvider.isInitialized = true; - - final List expected = hosts; - storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); - - doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertEquals(2, result.hosts.size()); - assertEquals(expected, result.hosts); - } - - @Test - void testGetTopology_withForceUpdate_returnsInitialHostList() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.clear(); - - doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); - - final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); - verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); - assertNotNull(result.hosts); - assertEquals( - Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("url").build()), - result.hosts); - } - - @Test - void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { - final List expectedMySQL = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("mysql").port(HostSpec.NO_PORT) - .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); - final List expectedPostgres = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) - .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); - when(mockTopologyUtils.queryForTopology(eq(mockConnection), any(HostSpec.class))).thenReturn(expectedMySQL).thenReturn(expectedPostgres); - - - rdsHostListProvider = getRdsHostListProvider("mysql://url/"); - - List hosts = rdsHostListProvider.queryForTopology(mockConnection); - assertEquals(expectedMySQL, hosts); - - rdsHostListProvider = getRdsHostListProvider("postgresql://url/"); - hosts = rdsHostListProvider.queryForTopology(mockConnection); - assertEquals(expectedPostgres, hosts); - } - - @Test - void testGetCachedTopology_returnStoredTopology() throws SQLException { - rdsHostListProvider = getRdsHostListProvider("jdbc:someprotocol://url"); - - final List expected = hosts; - storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); - - final List result = rdsHostListProvider.getStoredTopology(); - assertEquals(expected, result); - } - - @Test - void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - - assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); - - when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); - assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionNullTopology() throws SQLException { - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - rdsHostListProvider.clusterInstanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("?.pattern").build(); - - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); - doReturn(null).when(rdsHostListProvider).refresh(mockConnection); - doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); - - assertNull(rdsHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionHostNotInTopology() throws SQLException { - final List cachedTopology = Collections.singletonList( - new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build()); - - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); - doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); - doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); - - assertNull(rdsHostListProvider.identifyConnection(mockConnection)); - } - - @Test - void testIdentifyConnectionHostInTopology() throws SQLException { - final HostSpec expectedHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) - .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") - .port(HostSpec.NO_PORT) - .role(HostRole.WRITER) - .build(); - expectedHost.setHostId("instance-a-1"); - final List cachedTopology = Collections.singletonList(expectedHost); - - rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); - doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); - doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); - - final HostSpec actual = rdsHostListProvider.identifyConnection(mockConnection); - assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); - assertEquals("instance-a-1", actual.getHostId()); - } -} +// /* +// * Copyright 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. +// * 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 software.amazon.jdbc.hostlistprovider; +// +// import static org.junit.jupiter.api.Assertions.assertEquals; +// import static org.junit.jupiter.api.Assertions.assertNotNull; +// import static org.junit.jupiter.api.Assertions.assertNull; +// import static org.junit.jupiter.api.Assertions.assertThrows; +// import static org.mockito.ArgumentMatchers.any; +// import static org.mockito.ArgumentMatchers.eq; +// import static org.mockito.Mockito.atMostOnce; +// import static org.mockito.Mockito.doReturn; +// import static org.mockito.Mockito.never; +// import static org.mockito.Mockito.times; +// import static org.mockito.Mockito.verify; +// import static org.mockito.Mockito.when; +// +// import java.sql.Connection; +// import java.sql.SQLException; +// import java.util.ArrayList; +// import java.util.Arrays; +// import java.util.Collections; +// import java.util.List; +// import java.util.Properties; +// import org.junit.jupiter.api.AfterEach; +// import org.junit.jupiter.api.BeforeEach; +// import org.junit.jupiter.api.Test; +// import org.mockito.ArgumentCaptor; +// import org.mockito.Captor; +// import org.mockito.Mock; +// import org.mockito.Mockito; +// import org.mockito.MockitoAnnotations; +// import software.amazon.jdbc.HostRole; +// import software.amazon.jdbc.HostSpec; +// import software.amazon.jdbc.HostSpecBuilder; +// import software.amazon.jdbc.PluginService; +// import software.amazon.jdbc.dialect.TopologyDialect; +// import software.amazon.jdbc.hostavailability.HostAvailability; +// import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +// import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; +// import software.amazon.jdbc.util.FullServicesContainer; +// import software.amazon.jdbc.util.events.EventPublisher; +// import software.amazon.jdbc.util.storage.StorageService; +// import software.amazon.jdbc.util.storage.TestStorageServiceImpl; +// +// class RdsHostListProviderTest { +// private StorageService storageService; +// private RdsHostListProvider rdsHostListProvider; +// +// @Mock private Connection mockConnection; +// @Mock private FullServicesContainer mockServicesContainer; +// @Mock private PluginService mockPluginService; +// @Mock private HostListProviderService mockHostListProviderService; +// @Mock private HostSpecBuilder mockHostSpecBuilder; +// @Mock private EventPublisher mockEventPublisher; +// @Mock private TopologyUtils mockTopologyUtils; +// @Mock private TopologyDialect mockDialect; +// @Captor private ArgumentCaptor queryCaptor; +// +// private AutoCloseable closeable; +// private final HostSpec currentHostSpec = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) +// .host("foo").port(1234).build(); +// private final List hosts = Arrays.asList( +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host1").build(), +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host2").build()); +// +// @BeforeEach +// void setUp() throws SQLException { +// closeable = MockitoAnnotations.openMocks(this); +// storageService = new TestStorageServiceImpl(mockEventPublisher); +// when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); +// when(mockServicesContainer.getStorageService()).thenReturn(storageService); +// when(mockServicesContainer.getPluginService()).thenReturn(mockPluginService); +// when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); +// when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); +// when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); +// when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); +// when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); +// when(mockHostListProviderService.getHostSpecBuilder()) +// .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); +// when(mockHostListProviderService.getCurrentConnection()).thenReturn(mockConnection); +// } +// +// @AfterEach +// void tearDown() throws Exception { +// storageService.clearAll(); +// closeable.close(); +// } +// +// private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { +// RdsHostListProvider provider = new RdsHostListProvider( +// mockDialect, new Properties(), originalUrl, mockServicesContainer, mockTopologyUtils); +// provider.init(); +// return provider; +// } +// +// @Test +// void testGetTopology_returnCachedTopology() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("protocol://url/")); +// +// final List expected = hosts; +// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); +// +// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); +// assertEquals(expected, result.hosts); +// assertEquals(2, result.hosts.size()); +// verify(rdsHostListProvider, never()).queryForTopology(mockConnection); +// } +// +// @Test +// void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// rdsHostListProvider.isInitialized = true; +// +// storageService.set(rdsHostListProvider.clusterId, new Topology(hosts)); +// +// final List newHosts = Collections.singletonList( +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); +// doReturn(newHosts).when(mockTopologyUtils).queryForTopology(eq(mockConnection), any(HostSpec.class)); +// +// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); +// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); +// assertEquals(1, result.hosts.size()); +// assertEquals(newHosts, result.hosts); +// } +// +// @Test +// void testGetTopology_noForceUpdate_queryReturnsEmptyHostList() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// rdsHostListProvider.clusterId = "cluster-id"; +// rdsHostListProvider.isInitialized = true; +// +// final List expected = hosts; +// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); +// +// doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); +// +// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); +// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); +// assertEquals(2, result.hosts.size()); +// assertEquals(expected, result.hosts); +// } +// +// @Test +// void testGetTopology_withForceUpdate_returnsInitialHostList() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// rdsHostListProvider.clear(); +// +// doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); +// +// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); +// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); +// assertNotNull(result.hosts); +// assertEquals( +// Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("url").build()), +// result.hosts); +// } +// +// @Test +// void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { +// final List expectedMySQL = Collections.singletonList( +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("mysql").port(HostSpec.NO_PORT) +// .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); +// final List expectedPostgres = Collections.singletonList( +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) +// .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); +// when(mockTopologyUtils.queryForTopology(eq(mockConnection), any(HostSpec.class))).thenReturn(expectedMySQL).thenReturn(expectedPostgres); +// +// +// rdsHostListProvider = getRdsHostListProvider("mysql://url/"); +// +// List hosts = rdsHostListProvider.queryForTopology(mockConnection); +// assertEquals(expectedMySQL, hosts); +// +// rdsHostListProvider = getRdsHostListProvider("postgresql://url/"); +// hosts = rdsHostListProvider.queryForTopology(mockConnection); +// assertEquals(expectedPostgres, hosts); +// } +// +// @Test +// void testGetCachedTopology_returnStoredTopology() throws SQLException { +// rdsHostListProvider = getRdsHostListProvider("jdbc:someprotocol://url"); +// +// final List expected = hosts; +// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); +// +// final List result = rdsHostListProvider.getStoredTopology(); +// assertEquals(expected, result); +// } +// +// @Test +// void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// +// assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); +// +// when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); +// assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); +// } +// +// @Test +// void testIdentifyConnectionNullTopology() throws SQLException { +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// rdsHostListProvider.clusterInstanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) +// .host("?.pattern").build(); +// +// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); +// doReturn(null).when(rdsHostListProvider).refresh(mockConnection); +// doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); +// +// assertNull(rdsHostListProvider.identifyConnection(mockConnection)); +// } +// +// @Test +// void testIdentifyConnectionHostNotInTopology() throws SQLException { +// final List cachedTopology = Collections.singletonList( +// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) +// .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") +// .port(HostSpec.NO_PORT) +// .role(HostRole.WRITER) +// .build()); +// +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); +// doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); +// doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); +// +// assertNull(rdsHostListProvider.identifyConnection(mockConnection)); +// } +// +// @Test +// void testIdentifyConnectionHostInTopology() throws SQLException { +// final HostSpec expectedHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) +// .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") +// .port(HostSpec.NO_PORT) +// .role(HostRole.WRITER) +// .build(); +// expectedHost.setHostId("instance-a-1"); +// final List cachedTopology = Collections.singletonList(expectedHost); +// +// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); +// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); +// doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); +// doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); +// +// final HostSpec actual = rdsHostListProvider.identifyConnection(mockConnection); +// assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); +// assertEquals("instance-a-1", actual.getHostId()); +// } +// } From faf4394247a1959aae41a210e906d00fa5ac6063 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 4 Nov 2025 15:42:23 -0800 Subject: [PATCH 26/46] Adjusting for Global wip --- .../hostlistprovider/AuroraTopologyUtils.java | 113 ++++++++++++++++++ .../MultiAzTopologyUtils.java | 40 +++++++ .../jdbc/hostlistprovider/TopologyUtils.java | 72 +---------- 3 files changed, 158 insertions(+), 67 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java create mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java new file mode 100644 index 000000000..548fdf03f --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -0,0 +1,113 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.hostlistprovider; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.util.Messages; + +public class AuroraTopologyUtils extends TopologyUtils { + private static final Logger LOGGER = Logger.getLogger(AuroraTopologyUtils.class.getName()); + + public AuroraTopologyUtils(TopologyDialect dialect, HostSpec initialHostSpec, + HostSpecBuilder hostSpecBuilder) { + super(dialect, initialHostSpec, hostSpecBuilder); + } + + @Override + protected @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) throws SQLException { + if (rs.getMetaData().getColumnCount() == 0) { + // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. + LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); + return null; + } + + // Data is result set is ordered by last updated time so the latest records go last. + // When adding hosts to a map, the newer records replace the older ones. + List hosts = new ArrayList<>(); + while (rs.next()) { + try { + hosts.add(createHost(rs, hostTemplate)); + } catch (Exception e) { + LOGGER.finest( + Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + return null; + } + } + + return hosts; + } + + protected HostSpec createHost(ResultSet rs, HostSpec hostTemplate) throws SQLException { + + // According to the topology query the result set + // should contain 4 columns: node ID, 1/0 (writer/reader), CPU utilization, node lag in time. + String hostName = rs.getString(1); + final boolean isWriter = rs.getBoolean(2); + final double cpuUtilization = rs.getDouble(3); + final double nodeLag = rs.getDouble(4); + Timestamp lastUpdateTime; + try { + lastUpdateTime = rs.getTimestamp(5); + } catch (Exception e) { + lastUpdateTime = Timestamp.from(Instant.now()); + } + + // Calculate weight based on node lag in time and CPU utilization. + final long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); + + return createHost(hostName, isWriter, weight, lastUpdateTime, hostTemplate); + } + + protected HostSpec createHost(String host, boolean isWriter, long weight, Timestamp lastUpdateTime, + HostSpec clusterInstanceTemplate) { + host = host == null ? "?" : host; + final String endpoint = getHostEndpoint(host, clusterInstanceTemplate); + final int port = clusterInstanceTemplate.isPortSpecified() + ? clusterInstanceTemplate.getPort() + : this.initialHostSpec.getPort(); + + final HostSpec hostSpec = this.hostSpecBuilder + .host(endpoint) + .port(port) + .role(isWriter ? HostRole.WRITER : HostRole.READER) + .availability(HostAvailability.AVAILABLE) + .weight(weight) + .lastUpdateTime(lastUpdateTime) + .build(); + hostSpec.addAlias(host); + hostSpec.setHostId(host); + return hostSpec; + } + + protected String getHostEndpoint(final String nodeName, final HostSpec clusterInstanceTemplate) { + final String host = clusterInstanceTemplate.getHost(); + return host.replace("?", nodeName); + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java new file mode 100644 index 000000000..76efd32b3 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -0,0 +1,40 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.hostlistprovider; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.dialect.TopologyDialect; + +public class MultiAzTopologyUtils extends TopologyUtils { + public MultiAzTopologyUtils(TopologyDialect dialect, HostSpec initialHostSpec, + HostSpecBuilder hostSpecBuilder) { + super(dialect, initialHostSpec, hostSpecBuilder); + } + + @Override + protected @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) + throws SQLException { + + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index 95880e1af..af88b5761 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -41,7 +41,7 @@ import software.amazon.jdbc.util.SynchronousExecutor; import software.amazon.jdbc.util.Utils; -public class TopologyUtils { +public abstract class TopologyUtils { private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); protected static final int DEFAULT_QUERY_TIMEOUT_MS = 1000; @@ -63,17 +63,7 @@ public TopologyUtils( int networkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - List hosts = new ArrayList<>(); - List queryHosts = this.dialect.processTopologyResults(conn, resultSet); - if (Utils.isNullOrEmpty(queryHosts)) { - return null; - } - - for (TopologyQueryHostSpec queryHost : queryHosts) { - hosts.add(this.toHostspec(queryHost, hostTemplate)); - } - - return this.verifyWriter(hosts); + return this.verifyWriter(this.getHosts(conn, resultSet, hostTemplate)); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -83,41 +73,7 @@ public TopologyUtils( } } - public @Nullable List queryForTopology(Connection conn, Map hostTemplateByRegion) throws SQLException { - int networkTimeout = setNetworkTimeout(conn); - try (final Statement stmt = conn.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - List hosts = new ArrayList<>(); - List queryHosts = this.dialect.processTopologyResults(conn, resultSet); - if (Utils.isNullOrEmpty(queryHosts)) { - return null; - } - - for (TopologyQueryHostSpec queryHost : queryHosts) { - String region = queryHost.getRegion(); - if (region == null) { - throw new SQLException(""); - } - - HostSpec template = hostTemplateByRegion.get(region); - if (template == null) { - throw new SQLException(""); - } - - hosts.add(this.toHostspec(queryHost, template)); - } - - return this.verifyWriter(hosts); - } catch (final SQLSyntaxErrorException e) { - throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); - } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); - } - } - } - - private int setNetworkTimeout(Connection conn) { + protected int setNetworkTimeout(Connection conn) { int networkTimeout = -1; try { networkTimeout = conn.getNetworkTimeout(); @@ -132,6 +88,8 @@ private int setNetworkTimeout(Connection conn) { return networkTimeout; } + protected abstract @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) throws SQLException; + protected @Nullable List verifyWriter(List allHosts) { List hosts = new ArrayList<>(); List writers = new ArrayList<>(); @@ -160,26 +118,6 @@ private int setNetworkTimeout(Connection conn) { return hosts; } - protected HostSpec toHostspec(TopologyQueryHostSpec queryHost, HostSpec hostTemplate) { - final String instanceId = queryHost.getInstanceId() == null ? "?" : queryHost.getInstanceId(); - final String endpoint = hostTemplate.getHost().replace("?", instanceId); - final int port = hostTemplate.isPortSpecified() - ? hostTemplate.getPort() - : this.initialHostSpec.getPort(); - - final HostSpec hostSpec = this.hostSpecBuilder - .host(endpoint) - .port(port) - .role(queryHost.isWriter() ? HostRole.WRITER : HostRole.READER) - .availability(HostAvailability.AVAILABLE) - .weight(queryHost.getWeight()) - .lastUpdateTime(queryHost.getLastUpdateTime()) - .build(); - hostSpec.addAlias(instanceId); - hostSpec.setHostId(instanceId); - return hostSpec; - } - /** * Identifies instances across different database types using instanceId and instanceName values. * From 58a456f1dfbdfbbf513f6247e409e291d49a6fa0 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 4 Nov 2025 17:46:44 -0800 Subject: [PATCH 27/46] Build passing --- .../jdbc/dialect/AuroraDialectUtils.java | 96 -------------- .../jdbc/dialect/AuroraMysqlDialect.java | 29 +--- .../amazon/jdbc/dialect/AuroraPgDialect.java | 29 +--- .../dialect/GlobalAuroraDialectUtils.java | 63 --------- .../dialect/GlobalAuroraMysqlDialect.java | 36 +++-- .../jdbc/dialect/GlobalAuroraPgDialect.java | 31 ++--- ....java => GlobalAuroraTopologyDialect.java} | 4 +- .../jdbc/dialect/MultiAzClusterDialect.java | 23 ++++ .../dialect/MultiAzClusterMysqlDialect.java | 39 ++++-- .../jdbc/dialect/MultiAzClusterPgDialect.java | 39 +++--- .../jdbc/dialect/MultiAzDialectUtils.java | 124 ------------------ .../amazon/jdbc/dialect/TopologyDialect.java | 7 +- .../hostlistprovider/AuroraTopologyUtils.java | 61 ++++----- ...java => GlobalAuroraHostListProvider.java} | 32 +++-- .../GlobalAuroraTopologyUtils.java | 124 ++++++++++++++++++ .../MultiAzTopologyUtils.java | 89 ++++++++++++- .../hostlistprovider/RdsHostListProvider.java | 28 +--- .../jdbc/hostlistprovider/TopologyUtils.java | 50 +++++-- .../ClusterTopologyMonitorImpl.java | 44 +------ ....java => GlobalAuroraTopologyMonitor.java} | 33 ++--- ...nitoringGlobalAuroraHostListProvider.java} | 40 +++--- .../MonitoringRdsHostListProvider.java | 9 +- ..._advanced_jdbc_wrapper_messages.properties | 2 - 23 files changed, 463 insertions(+), 569 deletions(-) delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java rename wrapper/src/main/java/software/amazon/jdbc/dialect/{GlobalTopologyDialect.java => GlobalAuroraTopologyDialect.java} (83%) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterDialect.java delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java rename wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/{AuroraGlobalDbHostListProvider.java => GlobalAuroraHostListProvider.java} (73%) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java rename wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/{GlobalTopologyMonitor.java => GlobalAuroraTopologyMonitor.java} (66%) rename wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/{AuroraGlobalDbMonitoringHostListProvider.java => MonitoringGlobalAuroraHostListProvider.java} (67%) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java deleted file mode 100644 index ee1f4d145..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraDialectUtils.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.dialect; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.StringUtils; - -public class AuroraDialectUtils { - - protected final String writerIdQuery; - - private static final Logger LOGGER = Logger.getLogger(AuroraDialectUtils.class.getName()); - - public AuroraDialectUtils(String writerIdQuery) { - this.writerIdQuery = writerIdQuery; - } - - public @Nullable List processTopologyResults(ResultSet resultSet) - throws SQLException { - if (resultSet.getMetaData().getColumnCount() == 0) { - // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); - return null; - } - - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - List hosts = new ArrayList<>(); - while (resultSet.next()) { - try { - hosts.add(createHost(resultSet)); - } catch (Exception e) { - LOGGER.finest( - Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); - return null; - } - } - - return hosts; - } - - protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { - // According to the topology query the result set should contain 4 columns: - // instance ID, 1/0 (writer/reader), CPU utilization, instance lag - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final float cpuUtilization = resultSet.getFloat(3); - final float instanceLag = resultSet.getFloat(4); - Timestamp lastUpdateTime; - try { - lastUpdateTime = resultSet.getTimestamp(5); - } catch (Exception e) { - lastUpdateTime = Timestamp.from(Instant.now()); - } - - // Calculate weight based on instance lag in time and CPU utilization. - final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); - return new TopologyQueryHostSpec(hostName, isWriter, weight, lastUpdateTime); - } - - public boolean isWriterInstance(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { - if (resultSet.next()) { - return !StringUtils.isNullOrEmpty(resultSet.getString(1)); - } - } - } - - return false; - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 34eafc735..532c58343 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -24,7 +24,9 @@ import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; @@ -49,18 +51,6 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, + " table_schema = 'mysql' AND table_name = 'rds_topology'"; protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; - protected final AuroraDialectUtils dialectUtils; - - public AuroraMysqlDialect() { - super(); - this.dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); - } - - public AuroraMysqlDialect(AuroraDialectUtils dialectUtils) { - super(); - this.dialectUtils = dialectUtils; - } - @Override public boolean isDialect(final Connection connection) { try (Statement stmt = connection.createStatement(); @@ -85,10 +75,11 @@ public boolean isDialect(final Connection connection) { public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); + final TopologyUtils topologyUtils = new AuroraTopologyUtils(this, pluginService.getHostSpecBuilder()); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + return new MonitoringRdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); } - return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); + return new RdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); }; } @@ -97,20 +88,14 @@ public String getTopologyQuery() { return TOPOLOGY_QUERY; } - @Override - public @Nullable List processTopologyResults(Connection conn, ResultSet rs) - throws SQLException { - return this.dialectUtils.processTopologyResults(rs); - } - @Override public String getInstanceIdQuery() { return INSTANCE_ID_QUERY; } @Override - public boolean isWriterInstance(Connection connection) throws SQLException { - return dialectUtils.isWriterInstance(connection); + public String getWriterIdQuery() { + return WRITER_ID_QUERY; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 5984de366..6c605ce16 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -25,7 +25,9 @@ import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; @@ -65,18 +67,6 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); - protected final AuroraDialectUtils dialectUtils; - - public AuroraPgDialect() { - super(); - this.dialectUtils = new AuroraDialectUtils(WRITER_ID_QUERY); - } - - public AuroraPgDialect(AuroraDialectUtils dialectUtils) { - super(); - this.dialectUtils = dialectUtils; - } - @Override public boolean isDialect(final Connection connection) { if (!super.isDialect(connection)) { @@ -125,10 +115,11 @@ public List getDialectUpdateCandidates() { public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); + final TopologyUtils topologyUtils = new AuroraTopologyUtils(this, pluginService.getHostSpecBuilder()); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider(this, properties, initialUrl, servicesContainer); + return new MonitoringRdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); } - return new RdsHostListProvider(this, properties, initialUrl, servicesContainer); + return new RdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); }; } @@ -137,20 +128,14 @@ public String getTopologyQuery() { return TOPOLOGY_QUERY; } - @Override - public @Nullable List processTopologyResults(Connection conn, ResultSet rs) - throws SQLException { - return this.dialectUtils.processTopologyResults(rs); - } - @Override public String getInstanceIdQuery() { return INSTANCE_ID_QUERY; } @Override - public boolean isWriterInstance(Connection connection) throws SQLException { - return this.dialectUtils.isWriterInstance(connection); + public String getWriterIdQuery() { + return WRITER_ID_QUERY; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java deleted file mode 100644 index 294a614ac..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraDialectUtils.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.dialect; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; -import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.util.StringUtils; - -public class GlobalAuroraDialectUtils extends AuroraDialectUtils { - protected final String regionByInstanceIdQuery; - - public GlobalAuroraDialectUtils(String writerIdQuery, String regionByInstanceIdQuery) { - super(writerIdQuery); - this.regionByInstanceIdQuery = regionByInstanceIdQuery; - } - - @Override - protected TopologyQueryHostSpec createHost(final ResultSet resultSet) throws SQLException { - // The topology query results should contain 4 columns: - // instance ID, 1/0 (writer/reader), instance lag in time (ms), AWS region. - String hostName = resultSet.getString(1); - final boolean isWriter = resultSet.getBoolean(2); - final float instanceLag = resultSet.getFloat(3); - final String region = resultSet.getString(4); - - // Calculate weight based on instance lag in time and CPU utilization. - final long weight = Math.round(instanceLag) * 100L; - return new TopologyQueryHostSpec(hostName, isWriter, weight, Timestamp.from(Instant.now()), region); - } - - protected @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { - try (final PreparedStatement stmt = conn.prepareStatement(this.regionByInstanceIdQuery)) { - stmt.setString(1, instanceId); - try (final ResultSet resultSet = stmt.executeQuery()) { - if (resultSet.next()) { - String awsRegion = resultSet.getString(1); - return StringUtils.isNullOrEmpty(awsRegion) ? null : awsRegion; - } - } - } - - return null; - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java index 8ae3b33c1..44db2c5af 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java @@ -23,11 +23,12 @@ import java.util.Collections; import java.util.List; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.AuroraGlobalDbHostListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.AuroraGlobalDbMonitoringHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringGlobalAuroraHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; -public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect implements GlobalTopologyDialect { +public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect implements GlobalAuroraTopologyDialect { protected static final String GLOBAL_STATUS_TABLE_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" @@ -45,29 +46,26 @@ public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect implements Glob protected static final String REGION_BY_INSTANCE_ID_QUERY = "SELECT AWS_REGION FROM information_schema.aurora_global_db_instance_status WHERE SERVER_ID = ?"; - public GlobalAuroraMysqlDialect() { - super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY, REGION_BY_INSTANCE_ID_QUERY)); - } @Override public boolean isDialect(final Connection connection) { try { try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(this.GLOBAL_STATUS_TABLE_EXISTS_QUERY)) { + ResultSet rs = stmt.executeQuery(GLOBAL_STATUS_TABLE_EXISTS_QUERY)) { if (!rs.next()) { return false; } } try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(this.GLOBAL_INSTANCE_STATUS_EXISTS_QUERY)) { + ResultSet rs = stmt.executeQuery(GLOBAL_INSTANCE_STATUS_EXISTS_QUERY)) { if (!rs.next()) { return false; } } try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(this.REGION_COUNT_QUERY)) { + ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { if (rs.next()) { int awsRegionCount = rs.getInt(1); return awsRegionCount > 1; @@ -89,22 +87,22 @@ public boolean isDialect(final Connection connection) { public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); + final GlobalAuroraTopologyUtils topologyUtils = + new GlobalAuroraTopologyUtils(this, pluginService.getHostSpecBuilder()); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new AuroraGlobalDbMonitoringHostListProvider(this, properties, initialUrl, servicesContainer); + return new MonitoringGlobalAuroraHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); } - - return new AuroraGlobalDbHostListProvider(this, properties, initialUrl, servicesContainer); + return new GlobalAuroraHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); }; } @Override - public String getRegion(String instanceId, Connection conn) - throws SQLException { - if (!(this.dialectUtils instanceof GlobalAuroraDialectUtils)) { - throw new SQLException(""); - } + public String getTopologyQuery() { + return GLOBAL_TOPOLOGY_QUERY; + } - GlobalAuroraDialectUtils globalUtils = (GlobalAuroraDialectUtils) this.dialectUtils; - return globalUtils.getRegion(instanceId, conn); + @Override + public String getRegionByInstanceIdQuery() { + return REGION_BY_INSTANCE_ID_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java index 0956f40b0..5a623e714 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java @@ -24,11 +24,12 @@ import java.util.List; import java.util.logging.Logger; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.AuroraGlobalDbHostListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.AuroraGlobalDbMonitoringHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringGlobalAuroraHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; -public class GlobalAuroraPgDialect extends AuroraPgDialect implements GlobalTopologyDialect { +public class GlobalAuroraPgDialect extends AuroraPgDialect implements GlobalAuroraTopologyDialect { protected static final String GLOBAL_STATUS_FUNC_EXISTS_QUERY = "select 'aurora_global_db_status'::regproc"; protected static final String GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY = @@ -45,10 +46,6 @@ public class GlobalAuroraPgDialect extends AuroraPgDialect implements GlobalTopo private static final Logger LOGGER = Logger.getLogger(GlobalAuroraPgDialect.class.getName()); - public GlobalAuroraPgDialect() { - super(new GlobalAuroraDialectUtils(WRITER_ID_QUERY, REGION_BY_INSTANCE_ID_QUERY)); - } - @Override public boolean isDialect(final Connection connection) { try { @@ -102,22 +99,22 @@ public boolean isDialect(final Connection connection) { public HostListProviderSupplier getHostListProviderSupplier() { return (properties, initialUrl, servicesContainer) -> { final PluginService pluginService = servicesContainer.getPluginService(); + final GlobalAuroraTopologyUtils topologyUtils = + new GlobalAuroraTopologyUtils(this, pluginService.getHostSpecBuilder()); if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new AuroraGlobalDbMonitoringHostListProvider(this, properties, initialUrl, servicesContainer); + return new MonitoringGlobalAuroraHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); } - - return new AuroraGlobalDbHostListProvider(this, properties, initialUrl, servicesContainer); + return new GlobalAuroraHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); }; } @Override - public String getRegion(String instanceId, Connection conn) - throws SQLException { - if (!(this.dialectUtils instanceof GlobalAuroraDialectUtils)) { - throw new SQLException(""); - } + public String getTopologyQuery() { + return GLOBAL_TOPOLOGY_QUERY; + } - GlobalAuroraDialectUtils globalUtils = (GlobalAuroraDialectUtils) this.dialectUtils; - return globalUtils.getRegion(instanceId, conn); + @Override + public String getRegionByInstanceIdQuery() { + return REGION_BY_INSTANCE_ID_QUERY; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java similarity index 83% rename from wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java rename to wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java index c41c62ce5..1febb80a7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalTopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java @@ -19,6 +19,6 @@ import java.sql.Connection; import java.sql.SQLException; -public interface GlobalTopologyDialect extends TopologyDialect { - String getRegion(String instanceId, Connection conn) throws SQLException; +public interface GlobalAuroraTopologyDialect extends TopologyDialect { + String getRegionByInstanceIdQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterDialect.java new file mode 100644 index 000000000..4dc0a584d --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterDialect.java @@ -0,0 +1,23 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +public interface MultiAzClusterDialect extends TopologyDialect { + String getWriterIdQuery(); + + String getWriterIdColumnName(); +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index f142e8ab1..2da87edba 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -25,12 +25,19 @@ import java.util.Properties; import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; +import software.amazon.jdbc.hostlistprovider.MultiAzTopologyUtils; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover.FailoverRestriction; +import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class MultiAzClusterMysqlDialect extends MysqlDialect implements TopologyDialect { +public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzClusterDialect { protected static final String REPORT_HOST_EXISTS_QUERY = "SHOW VARIABLES LIKE 'report_host'"; protected static final String TOPOLOGY_TABLE_EXISTS_QUERY = @@ -45,15 +52,13 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements Topology + " WHERE id = @@server_id"; // For reader instances, the query returns a writer instance ID. For a writer instance, the query returns no data. protected static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; - protected static final String WRITER_ID_QUERY_COLUMN = "Source_Server_Id"; + protected static final String WRITER_ID_QUERY_COLUMN_NAME = "Source_Server_Id"; protected static final String IS_READER_QUERY = "SELECT @@read_only"; private static final EnumSet FAILOVER_RESTRICTIONS = EnumSet.of(FailoverRestriction.DISABLE_TASK_A, FailoverRestriction.ENABLE_WRITER_IN_TASK_B); protected final RdsUtils rdsUtils = new RdsUtils(); - protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - INSTANCE_ID_QUERY, WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN); @Override public boolean isDialect(final Connection connection) { @@ -94,7 +99,14 @@ public boolean isDialect(final Connection connection) { @Override public HostListProviderSupplier getHostListProviderSupplier() { - return this.dialectUtils.getHostListProviderSupplier(this); + return (properties, initialUrl, servicesContainer) -> { + final PluginService pluginService = servicesContainer.getPluginService(); + final TopologyUtils topologyUtils = new MultiAzTopologyUtils(this, pluginService.getHostSpecBuilder()); + if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { + return new MonitoringRdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); + } + return new RdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); + }; } @Override @@ -119,23 +131,22 @@ public String getTopologyQuery() { } @Override - public List processTopologyResults(Connection conn, ResultSet rs) - throws SQLException { - return dialectUtils.processTopologyResults(conn, rs); + public String getInstanceIdQuery() { + return INSTANCE_ID_QUERY; } @Override - public String getInstanceIdQuery() { - return INSTANCE_ID_QUERY; + public String getIsReaderQuery() { + return IS_READER_QUERY; } @Override - public boolean isWriterInstance(Connection connection) throws SQLException { - return dialectUtils.isWriterInstance(connection); + public String getWriterIdQuery() { + return WRITER_ID_QUERY; } @Override - public String getIsReaderQuery() { - return IS_READER_QUERY; + public String getWriterIdColumnName() { + return WRITER_ID_QUERY_COLUMN_NAME; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 7e31ba678..6547ea339 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -22,11 +22,17 @@ import java.sql.Statement; import java.util.List; import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.PluginService; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; +import software.amazon.jdbc.hostlistprovider.MultiAzTopologyUtils; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; +import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; +import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; -public class MultiAzClusterPgDialect extends PgDialect implements TopologyDialect { +public class MultiAzClusterPgDialect extends PgDialect implements MultiAzClusterDialect { protected static final String IS_RDS_CLUSTER_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()"; @@ -45,14 +51,11 @@ public class MultiAzClusterPgDialect extends PgDialect implements TopologyDialec "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()" + " WHERE multi_az_db_cluster_source_dbi_resource_id OPERATOR(pg_catalog.!=)" + " (SELECT dbi_resource_id FROM rds_tools.dbi_resource_id())"; - protected static final String WRITER_ID_QUERY_COLUMN = "multi_az_db_cluster_source_dbi_resource_id"; + protected static final String WRITER_ID_QUERY_COLUMN_NAME = "multi_az_db_cluster_source_dbi_resource_id"; protected static final String IS_READER_QUERY = "SELECT pg_catalog.pg_is_in_recovery()"; private static MultiAzDbClusterPgExceptionHandler exceptionHandler; - protected final MultiAzDialectUtils dialectUtils = new MultiAzDialectUtils( - INSTANCE_ID_QUERY, WRITER_ID_QUERY, WRITER_ID_QUERY_COLUMN); - @Override public boolean isDialect(final Connection connection) { try (Statement stmt = connection.createStatement(); @@ -79,7 +82,14 @@ public ExceptionHandler getExceptionHandler() { @Override public HostListProviderSupplier getHostListProviderSupplier() { - return this.dialectUtils.getHostListProviderSupplier(this); + return (properties, initialUrl, servicesContainer) -> { + final PluginService pluginService = servicesContainer.getPluginService(); + final TopologyUtils topologyUtils = new MultiAzTopologyUtils(this, pluginService.getHostSpecBuilder()); + if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { + return new MonitoringRdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); + } + return new RdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); + }; } @Override @@ -88,23 +98,22 @@ public String getTopologyQuery() { } @Override - public @Nullable List processTopologyResults(Connection conn, ResultSet rs) - throws SQLException { - return this.dialectUtils.processTopologyResults(conn, rs); + public String getInstanceIdQuery() { + return INSTANCE_ID_QUERY; } @Override - public String getInstanceIdQuery() { - return INSTANCE_ID_QUERY; + public String getIsReaderQuery() { + return IS_READER_QUERY; } @Override - public boolean isWriterInstance(Connection connection) throws SQLException { - return dialectUtils.isWriterInstance(connection); + public String getWriterIdQuery() { + return WRITER_ID_QUERY; } @Override - public String getIsReaderQuery() { - return IS_READER_QUERY; + public String getWriterIdColumnName() { + return WRITER_ID_QUERY_COLUMN_NAME; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java deleted file mode 100644 index 42d25a3ab..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzDialectUtils.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.dialect; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Timestamp; -import java.time.Instant; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; -import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; -import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; -import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.StringUtils; - -public class MultiAzDialectUtils { - private static final Logger LOGGER = Logger.getLogger(MultiAzDialectUtils.class.getName()); - private final String instanceIdQuery; - private final String writerIdQuery; - private final String writerIdQueryColumn; - - public MultiAzDialectUtils(String instanceIdQuery, String writerIdQuery, String writerIdQueryColumn) { - this.instanceIdQuery = instanceIdQuery; - this.writerIdQuery = writerIdQuery; - this.writerIdQueryColumn = writerIdQueryColumn; - } - - public @Nullable List processTopologyResults(Connection conn, ResultSet resultSet) - throws SQLException { - List hosts = new ArrayList<>(); - while (resultSet.next()) { - try { - final TopologyQueryHostSpec host = createHost(resultSet, this.getWriterId(conn)); - hosts.add(host); - } catch (Exception e) { - LOGGER.finest( - Messages.get("MultiAzDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); - return null; - } - } - - return hosts; - } - - protected TopologyQueryHostSpec createHost( - final ResultSet resultSet, - final @Nullable String writerId) throws SQLException { - - String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" - String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" - String hostId = resultSet.getString("id"); // "1034958454" - final boolean isWriter = hostId.equals(writerId); - - return new TopologyQueryHostSpec(instanceName, isWriter, 0, Timestamp.from(Instant.now())); - } - - protected @Nullable String getWriterId(Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { - if (resultSet.next()) { - String writerId = resultSet.getString(this.writerIdQueryColumn); - if (!StringUtils.isNullOrEmpty(writerId)) { - return writerId; - } - } - } - - // Replica status doesn't exist, which means that this instance is a writer. We execute instanceIdQuery to get the - // ID of this writer instance. - try (final ResultSet resultSet = stmt.executeQuery(this.instanceIdQuery)) { - if (resultSet.next()) { - return resultSet.getString(1); - } - } - } - return null; - } - - public boolean isWriterInstance(final Connection connection) throws SQLException { - try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.writerIdQuery)) { - if (resultSet.next()) { - String instanceId = resultSet.getString(this.writerIdQueryColumn); - // The writer ID is only returned when connected to a reader, so if the query does not return a value, it - // means we are connected to a writer. - return StringUtils.isNullOrEmpty(instanceId); - } - } - } - return false; - } - - protected HostListProviderSupplier getHostListProviderSupplier(TopologyDialect dialect) { - return (properties, initialUrl, servicesContainer) -> { - final PluginService pluginService = servicesContainer.getPluginService(); - if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { - return new MonitoringRdsHostListProvider(dialect, properties, initialUrl, servicesContainer); - - } else { - return new RdsHostListProvider(dialect, properties, initialUrl, servicesContainer); - } - }; - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index f3fb5a9ff..84ac7ae3a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -25,14 +25,9 @@ public interface TopologyDialect extends Dialect { String getTopologyQuery(); - @Nullable - List processTopologyResults(Connection conn, ResultSet rs) throws SQLException; - String getInstanceIdQuery(); - // TODO: dialects have an isWriterInstance method (uses is_writer query) and a getHostRole method - // (uses is_reader query). Can we merge them into one getHostRole method? - boolean isWriterInstance(final Connection connection) throws SQLException; + String getWriterIdQuery(); String getIsReaderQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java index 548fdf03f..79229e9cc 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -19,6 +19,7 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; @@ -31,29 +32,24 @@ import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.StringUtils; public class AuroraTopologyUtils extends TopologyUtils { private static final Logger LOGGER = Logger.getLogger(AuroraTopologyUtils.class.getName()); - public AuroraTopologyUtils(TopologyDialect dialect, HostSpec initialHostSpec, - HostSpecBuilder hostSpecBuilder) { - super(dialect, initialHostSpec, hostSpecBuilder); + public AuroraTopologyUtils(TopologyDialect dialect, HostSpecBuilder hostSpecBuilder) { + super(dialect, hostSpecBuilder); } @Override - protected @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) throws SQLException { - if (rs.getMetaData().getColumnCount() == 0) { - // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); - return null; - } - + protected @Nullable List getHosts( + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException { // Data is result set is ordered by last updated time so the latest records go last. // When adding hosts to a map, the newer records replace the older ones. List hosts = new ArrayList<>(); while (rs.next()) { try { - hosts.add(createHost(rs, hostTemplate)); + hosts.add(createHost(rs, initialHostSpec, hostTemplate)); } catch (Exception e) { LOGGER.finest( Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); @@ -64,7 +60,20 @@ public AuroraTopologyUtils(TopologyDialect dialect, HostSpec initialHostSpec, return hosts; } - protected HostSpec createHost(ResultSet rs, HostSpec hostTemplate) throws SQLException { + @Override + public boolean isWriterInstance(final Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (resultSet.next()) { + return !StringUtils.isNullOrEmpty(resultSet.getString(1)); + } + } + } + + return false; + } + + protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException { // According to the topology query the result set // should contain 4 columns: node ID, 1/0 (writer/reader), CPU utilization, node lag in time. @@ -82,32 +91,6 @@ protected HostSpec createHost(ResultSet rs, HostSpec hostTemplate) throws SQLExc // Calculate weight based on node lag in time and CPU utilization. final long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); - return createHost(hostName, isWriter, weight, lastUpdateTime, hostTemplate); - } - - protected HostSpec createHost(String host, boolean isWriter, long weight, Timestamp lastUpdateTime, - HostSpec clusterInstanceTemplate) { - host = host == null ? "?" : host; - final String endpoint = getHostEndpoint(host, clusterInstanceTemplate); - final int port = clusterInstanceTemplate.isPortSpecified() - ? clusterInstanceTemplate.getPort() - : this.initialHostSpec.getPort(); - - final HostSpec hostSpec = this.hostSpecBuilder - .host(endpoint) - .port(port) - .role(isWriter ? HostRole.WRITER : HostRole.READER) - .availability(HostAvailability.AVAILABLE) - .weight(weight) - .lastUpdateTime(lastUpdateTime) - .build(); - hostSpec.addAlias(host); - hostSpec.setHostId(host); - return hostSpec; - } - - protected String getHostEndpoint(final String nodeName, final HostSpec clusterInstanceTemplate) { - final String host = clusterInstanceTemplate.getHost(); - return host.replace("?", nodeName); + return createHost(hostName, hostName, isWriter, weight, lastUpdateTime, initialHostSpec, hostTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java similarity index 73% rename from wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java index e8c0fae65..d5f09dec6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraGlobalDbHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java @@ -16,11 +16,10 @@ package software.amazon.jdbc.hostlistprovider; -import java.sql.ResultSet; +import java.sql.Connection; import java.sql.SQLException; -import java.sql.Timestamp; -import java.time.Instant; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; @@ -29,16 +28,15 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class AuroraGlobalDbHostListProvider extends RdsHostListProvider { +public class GlobalAuroraHostListProvider extends RdsHostListProvider { - static final Logger LOGGER = Logger.getLogger(AuroraGlobalDbHostListProvider.class.getName()); + static final Logger LOGGER = Logger.getLogger(GlobalAuroraHostListProvider.class.getName()); public static final AwsWrapperProperty GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS = new AwsWrapperProperty( @@ -51,16 +49,18 @@ public class AuroraGlobalDbHostListProvider extends RdsHostListProvider { + "Each region in the Global Aurora Database should be specified in the list."); protected final RdsUtils rdsUtils = new RdsUtils(); + protected final GlobalAuroraTopologyUtils topologyUtils; - protected Map globalClusterInstanceTemplateByAwsRegion; + protected Map instanceTemplatesByRegion; static { - PropertyDefinition.registerPluginProperties(AuroraGlobalDbHostListProvider.class); + PropertyDefinition.registerPluginProperties(GlobalAuroraHostListProvider.class); } - public AuroraGlobalDbHostListProvider( - TopologyDialect dialect, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); + public GlobalAuroraHostListProvider( + GlobalAuroraTopologyUtils topologyUtils, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { + super(topologyUtils, properties, originalUrl, servicesContainer); + this.topologyUtils = topologyUtils; } @Override @@ -73,7 +73,7 @@ protected void initSettings() throws SQLException { } HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); - this.globalClusterInstanceTemplateByAwsRegion = Arrays.stream(templates.split(",")) + this.instanceTemplatesByRegion = Arrays.stream(templates.split(",")) .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) .collect(Collectors.toMap( Pair::getValue1, @@ -82,9 +82,15 @@ protected void initSettings() throws SQLException { return v.getValue2(); })); LOGGER.finest(() -> "Recognized GDB instance template patterns:\n" - + this.globalClusterInstanceTemplateByAwsRegion.entrySet().stream() + + this.instanceTemplatesByRegion.entrySet().stream() .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) .collect(Collectors.joining("\n")) ); } + + @Override + protected List queryForTopology(final Connection conn) throws SQLException { + init(); + return this.topologyUtils.queryForTopology(conn, this.initialHostSpec, this.instanceTemplatesByRegion); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java new file mode 100644 index 000000000..528e8b94c --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -0,0 +1,124 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.hostlistprovider; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLSyntaxErrorException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.dialect.GlobalAuroraTopologyDialect; +import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.StringUtils; + +public class GlobalAuroraTopologyUtils extends AuroraTopologyUtils { + private static final Logger LOGGER = Logger.getLogger(GlobalAuroraTopologyUtils.class.getName()); + + protected final GlobalAuroraTopologyDialect dialect; + + public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBuilder hostSpecBuilder) { + super(dialect, hostSpecBuilder); + this.dialect = dialect; + } + + public @Nullable List queryForTopology( + Connection conn, HostSpec initialHostSpec, Map instanceTemplatesByRegion) + throws SQLException { + int networkTimeout = setNetworkTimeout(conn); + try (final Statement stmt = conn.createStatement(); + final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { + if (resultSet.getMetaData().getColumnCount() == 0) { + // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. + LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); + return null; + } + + return this.verifyWriter(this.getHosts(conn, resultSet, initialHostSpec, instanceTemplatesByRegion)); + } catch (final SQLSyntaxErrorException e) { + throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); + } finally { + if (networkTimeout == 0 && !conn.isClosed()) { + conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); + } + } + } + + protected @Nullable List getHosts( + Connection conn, ResultSet rs, HostSpec initialHostSpec, Map instanceTemplatesByRegion) + throws SQLException { + // Data is result set is ordered by last updated time so the latest records go last. + // When adding hosts to a map, the newer records replace the older ones. + List hosts = new ArrayList<>(); + while (rs.next()) { + try { + hosts.add(createHost(rs, initialHostSpec, instanceTemplatesByRegion)); + } catch (Exception e) { + LOGGER.finest( + Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + return null; + } + } + + return hosts; + } + + protected HostSpec createHost( + ResultSet rs, HostSpec initialHostSpec, Map instanceTemplatesByRegion) + throws SQLException { + // According to the topology query the result set + // should contain 4 columns: node ID, 1/0 (writer/reader), node lag in time (msec), AWS region. + String hostName = rs.getString(1); + final boolean isWriter = rs.getBoolean(2); + final float nodeLag = rs.getFloat(3); + final String awsRegion = rs.getString(4); + + // Calculate weight based on node lag in time and CPU utilization. + final long weight = Math.round(nodeLag) * 100L; + + final HostSpec clusterInstanceTemplateForRegion = instanceTemplatesByRegion.get(awsRegion); + if (clusterInstanceTemplateForRegion == null) { + throw new SQLException("Can't find cluster template for region " + awsRegion); + } + + return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, clusterInstanceTemplateForRegion); + } + + public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { + try (final PreparedStatement stmt = conn.prepareStatement(this.dialect.getRegionByInstanceIdQuery())) { + stmt.setString(1, instanceId); + try (final ResultSet resultSet = stmt.executeQuery()) { + if (resultSet.next()) { + String awsRegion = resultSet.getString(1); + return StringUtils.isNullOrEmpty(awsRegion) ? null : awsRegion; + } + } + } + + return null; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 76efd32b3..1f0494fa7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -19,22 +19,103 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.time.Instant; +import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.dialect.MultiAzClusterDialect; import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.dialect.TopologyQueryHostSpec; +import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.StringUtils; public class MultiAzTopologyUtils extends TopologyUtils { - public MultiAzTopologyUtils(TopologyDialect dialect, HostSpec initialHostSpec, - HostSpecBuilder hostSpecBuilder) { - super(dialect, initialHostSpec, hostSpecBuilder); + private static final Logger LOGGER = Logger.getLogger(MultiAzTopologyUtils.class.getName()); + + protected final MultiAzClusterDialect dialect; + + public MultiAzTopologyUtils(MultiAzClusterDialect dialect, HostSpecBuilder hostSpecBuilder) { + super(dialect, hostSpecBuilder); + this.dialect = dialect; } @Override - protected @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) + protected @Nullable List getHosts( + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException { + String writerId = this.getWriterId(conn); + + // Data is result set is ordered by last updated time so the latest records go last. + // When adding hosts to a map, the newer records replace the older ones. + List hosts = new ArrayList<>(); + while (rs.next()) { + try { + hosts.add(createHost(rs, initialHostSpec, hostTemplate, writerId)); + } catch (Exception e) { + LOGGER.finest( + Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + return null; + } + } + + return hosts; + } + + @Override + public boolean isWriterInstance(final Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (resultSet.next()) { + String instanceId = resultSet.getString(this.dialect.getWriterIdColumnName()); + // The writer ID is only returned when connected to a reader, so if the query does not return a value, it + // means we are connected to a writer. + return StringUtils.isNullOrEmpty(instanceId); + } + } + } + return false; + } + + + protected @Nullable String getWriterId(Connection connection) throws SQLException { + try (final Statement stmt = connection.createStatement()) { + try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (resultSet.next()) { + String writerId = resultSet.getString(this.dialect.getWriterIdColumnName()); + if (!StringUtils.isNullOrEmpty(writerId)) { + return writerId; + } + } + } + + // Replica status doesn't exist, which means that this instance is a writer. We execute instanceIdQuery to get the + // ID of this writer instance. + try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { + if (resultSet.next()) { + return resultSet.getString(1); + } + } + } + return null; + } + + protected HostSpec createHost( + final ResultSet resultSet, + final HostSpec initialHostSpec, + final HostSpec hostTemplate, + final @Nullable String writerId) throws SQLException { + + String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" + String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" + String hostId = resultSet.getString("id"); // "1034958454" + final boolean isWriter = hostId.equals(writerId); + return createHost(hostId, instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, hostTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index a1e6ab18c..6baf6ed01 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -32,7 +32,6 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Messages; @@ -73,11 +72,11 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected static final int defaultTopologyQueryTimeoutMs = 5000; protected final ReentrantLock lock = new ReentrantLock(); - protected final TopologyDialect dialect; protected final Properties properties; protected final String originalUrl; protected final FullServicesContainer servicesContainer; protected final HostListProviderService hostListProviderService; + protected final TopologyUtils topologyUtils; protected RdsUrlType rdsUrlType; protected long refreshRateNano = CLUSTER_TOPOLOGY_REFRESH_RATE_MS.defaultValue != null @@ -88,7 +87,6 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected HostSpec initialHostSpec; protected String clusterId; protected HostSpec clusterInstanceTemplate; - protected TopologyUtils topologyUtils; protected volatile boolean isInitialized = false; @@ -97,28 +95,17 @@ public class RdsHostListProvider implements DynamicHostListProvider { } public RdsHostListProvider( - final TopologyDialect dialect, + final TopologyUtils topologyUtils, final Properties properties, final String originalUrl, final FullServicesContainer servicesContainer) { - this.dialect = dialect; + this.topologyUtils = topologyUtils; this.properties = properties; this.originalUrl = originalUrl; this.servicesContainer = servicesContainer; this.hostListProviderService = servicesContainer.getHostListProviderService(); } - // For testing purposes only - public RdsHostListProvider( - final TopologyDialect dialect, - final Properties properties, - final String originalUrl, - final FullServicesContainer servicesContainer, - final TopologyUtils topologyUtils) { - this(dialect, properties, originalUrl, servicesContainer); - this.topologyUtils = topologyUtils; - } - protected void init() throws SQLException { if (this.isInitialized) { return; @@ -170,13 +157,6 @@ protected void initSettings() throws SQLException { validateHostPatternSetting(this.clusterInstanceTemplate.getHost()); this.rdsUrlType = rdsHelper.identifyRdsType(this.initialHostSpec.getHost()); - - if (this.topologyUtils == null) { - this.topologyUtils = new TopologyUtils( - this.dialect, - this.initialHostSpec, - this.servicesContainer.getPluginService().getHostSpecBuilder()); - } } /** @@ -232,7 +212,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f */ protected List queryForTopology(final Connection conn) throws SQLException { init(); - return this.topologyUtils.queryForTopology(conn, this.clusterInstanceTemplate); + return this.topologyUtils.queryForTopology(conn, this.initialHostSpec, this.clusterInstanceTemplate); } /** diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index af88b5761..8d24d3614 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -22,6 +22,7 @@ import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; import java.sql.Statement; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -47,23 +48,27 @@ public abstract class TopologyUtils { protected final Executor networkTimeoutExecutor = new SynchronousExecutor(); protected final TopologyDialect dialect; - protected final HostSpec initialHostSpec; protected final HostSpecBuilder hostSpecBuilder; public TopologyUtils( TopologyDialect dialect, - HostSpec initialHostSpec, HostSpecBuilder hostSpecBuilder) { this.dialect = dialect; - this.initialHostSpec = initialHostSpec; this.hostSpecBuilder = hostSpecBuilder; } - public @Nullable List queryForTopology(Connection conn, HostSpec hostTemplate) throws SQLException { + public @Nullable List queryForTopology(Connection conn, HostSpec initialHostSpec, HostSpec hostTemplate) + throws SQLException { int networkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - return this.verifyWriter(this.getHosts(conn, resultSet, hostTemplate)); + if (resultSet.getMetaData().getColumnCount() == 0) { + // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. + LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); + return null; + } + + return this.verifyWriter(this.getHosts(conn, resultSet, initialHostSpec, hostTemplate)); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -88,7 +93,8 @@ protected int setNetworkTimeout(Connection conn) { return networkTimeout; } - protected abstract @Nullable List getHosts(Connection conn, ResultSet rs, HostSpec hostTemplate) throws SQLException; + protected abstract @Nullable List getHosts( + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException; protected @Nullable List verifyWriter(List allHosts) { List hosts = new ArrayList<>(); @@ -118,6 +124,34 @@ protected int setNetworkTimeout(Connection conn) { return hosts; } + public HostSpec createHost( + String instanceId, + String instanceName, + final boolean isWriter, + final long weight, + final Timestamp lastUpdateTime, + final HostSpec initialHostSpec, + final HostSpec clusterInstanceTemplate) { + instanceName = instanceName == null ? "?" : instanceName; + final String endpoint = clusterInstanceTemplate.getHost().replace("?", instanceName); + final int port = clusterInstanceTemplate.isPortSpecified() + ? clusterInstanceTemplate.getPort() + : initialHostSpec.getPort(); + + final HostSpec hostSpec = this.hostSpecBuilder + .hostId(instanceId) + .host(endpoint) + .port(port) + .role(isWriter ? HostRole.WRITER : HostRole.READER) + .availability(HostAvailability.AVAILABLE) + .weight(weight) + .lastUpdateTime(lastUpdateTime) + .build(); + hostSpec.addAlias(instanceName); + hostSpec.setHostId(instanceName); + return hostSpec; + } + /** * Identifies instances across different database types using instanceId and instanceName values. * @@ -144,9 +178,7 @@ protected int setNetworkTimeout(Connection conn) { return null; } - public boolean isWriterInstance(Connection connection) throws SQLException { - return this.dialect.isWriterInstance(connection); - } + public abstract boolean isWriterInstance(Connection connection) throws SQLException; public HostRole getHostRole(Connection conn) throws SQLException { try (final Statement stmt = conn.createStatement(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index d86c6bf90..cea64fa52 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -19,7 +19,6 @@ import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Properties; @@ -39,7 +38,6 @@ import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.hostlistprovider.Topology; import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.ExecutorFactory; @@ -174,7 +172,6 @@ public List forceRefresh(final boolean shouldVerifyWriter, final long @Override public List forceRefresh(@Nullable Connection connection, final long timeoutMs) throws SQLException, TimeoutException { - if (this.isVerifiedWriterConnection) { // Push monitoring thread to refresh topology with a verified connection return this.waitTillTopologyGetsUpdated(timeoutMs); @@ -522,11 +519,12 @@ protected List openAnyConnectionAndUpdateTopology() { new Object[]{this.writerHostSpec.get().getHost()})); } else { final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); + // TODO: this code isn't quite right when compared to main-3.x if (pair != null) { HostSpec hostTemplate = this.getClusterInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); HostSpec writerHost = - this.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, hostTemplate); + this.topologyUtils.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, hostTemplate); this.writerHostSpec.set(writerHost); LOGGER.finest( Messages.get( @@ -623,7 +621,7 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { } protected List queryForTopology(Connection connection) throws SQLException { - return this.topologyUtils.queryForTopology(connection, this.clusterInstanceTemplate); + return this.topologyUtils.queryForTopology(connection, this.initialHostSpec, this.clusterInstanceTemplate); } protected void updateTopologyCache(final @NonNull List hosts) { @@ -638,39 +636,6 @@ protected void updateTopologyCache(final @NonNull List hosts) { } } - protected HostSpec createHost( - String nodeId, - String nodeName, - final boolean isWriter, - final long weight, - final Timestamp lastUpdateTime, - final HostSpec clusterInstanceTemplate) { - - nodeName = nodeName == null ? "?" : nodeName; - final String endpoint = getHostEndpoint(nodeName, clusterInstanceTemplate); - final int port = clusterInstanceTemplate.isPortSpecified() - ? clusterInstanceTemplate.getPort() - : this.initialHostSpec.getPort(); - - final HostSpec hostSpec = this.servicesContainer.getHostListProviderService().getHostSpecBuilder() - .hostId(nodeId) - .host(endpoint) - .port(port) - .role(isWriter ? HostRole.WRITER : HostRole.READER) - .availability(HostAvailability.AVAILABLE) - .weight(weight) - .lastUpdateTime(lastUpdateTime) - .build(); - hostSpec.addAlias(nodeName); - hostSpec.setHostId(nodeName); - return hostSpec; - } - - protected String getHostEndpoint(final String nodeName, final HostSpec clusterInstanceTemplate) { - final String host = clusterInstanceTemplate.getHost(); - return host.replace("?", nodeName); - } - private static class NodeMonitoringWorker implements Runnable { private static final Logger LOGGER = Logger.getLogger(NodeMonitoringWorker.class.getName()); @@ -799,7 +764,8 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla List hosts; try { - hosts = this.monitor.topologyUtils.queryForTopology(connection, this.monitor.clusterInstanceTemplate); + hosts = this.monitor.topologyUtils.queryForTopology( + connection, this.monitor.initialHostSpec, this.monitor.clusterInstanceTemplate); if (hosts == null) { return; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java similarity index 66% rename from wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java index 8bca82a44..fa8680060 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalTopologyMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java @@ -18,34 +18,30 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.dialect.GlobalTopologyDialect; -import software.amazon.jdbc.hostlistprovider.TopologyUtils; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.StringUtils; -public class GlobalTopologyMonitor extends ClusterTopologyMonitorImpl { +public class GlobalAuroraTopologyMonitor extends ClusterTopologyMonitorImpl { + protected final Map instanceTemplatesByRegion; + protected final GlobalAuroraTopologyUtils topologyUtils; - private static final Logger LOGGER = Logger.getLogger(GlobalTopologyMonitor.class.getName()); - - protected final Map hostTemplatesByRegion; - protected final GlobalTopologyDialect dialect; - - public GlobalTopologyMonitor( + public GlobalAuroraTopologyMonitor( final FullServicesContainer servicesContainer, - final TopologyUtils topologyUtils, - final GlobalTopologyDialect dialect, + final GlobalAuroraTopologyUtils topologyUtils, final String clusterId, final HostSpec initialHostSpec, final Properties properties, final HostSpec clusterInstanceTemplate, final long refreshRateNano, final long highRefreshRateNano, - final Map hostTemplatesByRegion) { + final Map instanceTemplatesByRegion) { super(servicesContainer, topologyUtils, clusterId, @@ -55,15 +51,15 @@ public GlobalTopologyMonitor( refreshRateNano, highRefreshRateNano); - this.hostTemplatesByRegion = hostTemplatesByRegion; - this.dialect = dialect; + this.instanceTemplatesByRegion = instanceTemplatesByRegion; + this.topologyUtils = topologyUtils; } @Override protected HostSpec getClusterInstanceTemplate(String instanceId, Connection connection) throws SQLException { - String region = dialect.getRegion(instanceId, connection); + String region = this.topologyUtils.getRegion(instanceId, connection); if (!StringUtils.isNullOrEmpty(region)) { - final HostSpec clusterInstanceTemplateForRegion = this.hostTemplatesByRegion.get(region); + final HostSpec clusterInstanceTemplateForRegion = this.instanceTemplatesByRegion.get(region); if (clusterInstanceTemplateForRegion == null) { throw new SQLException("Can't find cluster template for region " + region); } @@ -73,4 +69,9 @@ protected HostSpec getClusterInstanceTemplate(String instanceId, Connection conn return this.clusterInstanceTemplate; } + + @Override + protected List queryForTopology(Connection connection) throws SQLException { + return this.topologyUtils.queryForTopology(connection, this.initialHostSpec, this.instanceTemplatesByRegion); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java similarity index 67% rename from wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java rename to wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java index 9fa906664..84395e0fc 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/AuroraGlobalDbMonitoringHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java @@ -16,9 +16,11 @@ package software.amazon.jdbc.hostlistprovider.monitoring; +import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; @@ -26,48 +28,49 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.dialect.GlobalTopologyDialect; -import software.amazon.jdbc.hostlistprovider.AuroraGlobalDbHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraHostListProvider; +import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; -public class AuroraGlobalDbMonitoringHostListProvider extends MonitoringRdsHostListProvider { +public class MonitoringGlobalAuroraHostListProvider extends MonitoringRdsHostListProvider { - static final Logger LOGGER = Logger.getLogger(AuroraGlobalDbMonitoringHostListProvider.class.getName()); + static final Logger LOGGER = Logger.getLogger(MonitoringGlobalAuroraHostListProvider.class.getName()); - protected Map globalClusterInstanceTemplateByAwsRegion = new HashMap<>(); + protected Map instanceTemplatesByRegion = new HashMap<>(); protected final RdsUtils rdsUtils = new RdsUtils(); - protected final GlobalTopologyDialect globalDialect; + protected final GlobalAuroraTopologyUtils topologyUtils; static { // Register property definition in AuroraGlobalDbHostListProvider class. It's not a mistake. - PropertyDefinition.registerPluginProperties(AuroraGlobalDbHostListProvider.class); + PropertyDefinition.registerPluginProperties(GlobalAuroraHostListProvider.class); } - public AuroraGlobalDbMonitoringHostListProvider( - GlobalTopologyDialect dialect, + public MonitoringGlobalAuroraHostListProvider( + GlobalAuroraTopologyUtils topologyUtils, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); - this.globalDialect = dialect; + super(topologyUtils, properties, originalUrl, servicesContainer); + this.topologyUtils = topologyUtils; } @Override protected void initSettings() throws SQLException { super.initSettings(); - String templates = AuroraGlobalDbHostListProvider.GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); + // TODO: check if we have other places that parse into string-HostSpec maps, consider refactoring + String templates = GlobalAuroraHostListProvider.GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); if (StringUtils.isNullOrEmpty(templates)) { throw new SQLException("Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database."); } HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); - this.globalClusterInstanceTemplateByAwsRegion = Arrays.stream(templates.split(",")) + this.instanceTemplatesByRegion = Arrays.stream(templates.split(",")) .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) .collect(Collectors.toMap( Pair::getValue1, @@ -76,7 +79,7 @@ protected void initSettings() throws SQLException { return v.getValue2(); })); LOGGER.finest(() -> "Recognized GDB instance template patterns:\n" - + this.globalClusterInstanceTemplateByAwsRegion.entrySet().stream() + + this.instanceTemplatesByRegion.entrySet().stream() .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) .collect(Collectors.joining("\n")) ); @@ -89,17 +92,20 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.servicesContainer, this.properties, (servicesContainer) -> - new GlobalTopologyMonitor( + new GlobalAuroraTopologyMonitor( servicesContainer, this.topologyUtils, - this.globalDialect, this.clusterId, this.initialHostSpec, this.properties, this.clusterInstanceTemplate, this.refreshRateNano, this.highRefreshRateNano, - this.globalClusterInstanceTemplateByAwsRegion)); + this.instanceTemplatesByRegion)); } + @Override + protected List queryForTopology(Connection connection) throws SQLException { + return this.topologyUtils.queryForTopology(connection, this.initialHostSpec, this.instanceTemplatesByRegion); + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index 7578ee210..4f1a82080 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -28,12 +28,9 @@ import software.amazon.jdbc.PluginService; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.cleanup.CanReleaseResources; -import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; -import software.amazon.jdbc.hostlistprovider.Topology; +import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.monitoring.MonitorService; -import software.amazon.jdbc.util.storage.StorageService; public class MonitoringRdsHostListProvider extends RdsHostListProvider implements BlockingHostListProvider, CanReleaseResources { @@ -53,11 +50,11 @@ public class MonitoringRdsHostListProvider protected final long highRefreshRateNano; public MonitoringRdsHostListProvider( - final TopologyDialect dialect, + final TopologyUtils topologyUtils, final Properties properties, final String originalUrl, final FullServicesContainer servicesContainer) { - super(dialect, properties, originalUrl, servicesContainer); + super(topologyUtils, properties, originalUrl, servicesContainer); this.servicesContainer = servicesContainer; this.pluginService = servicesContainer.getPluginService(); this.highRefreshRateNano = TimeUnit.MILLISECONDS.toNanos( diff --git a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties index 42efb7bd4..2cdc48935 100644 --- a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties +++ b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties @@ -269,8 +269,6 @@ MonitorServiceImpl.stopAndRemoveMissingMonitorType=The monitor service received MonitorServiceImpl.stopAndRemoveMonitorsMissingType=The monitor service received a request to stop all monitors with type ''{0}'', but the monitor service does not have any monitors registered under the given type. Please ensure monitors are registered under the correct type. MonitorServiceImpl.unexpectedMonitorClass=Monitor type mismatch - the monitor ''{0}'' was unexpectedly found under the ''{1}'' monitor class category. Please verify that monitors are submitted under their concrete class. -MultiAzDialectUtils.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} - NodeMonitoringThread.detectedWriter=Writer detected by node monitoring thread: ''{0}''. NodeMonitoringThread.invalidWriterQuery=The writer topology query is invalid: {0} NodeMonitoringThread.threadCompleted=Node monitoring thread completed in {0} ms. From 66dc4fc7612ffb1a5187b780a1335eabf1a7755f Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 11:27:08 -0800 Subject: [PATCH 28/46] Fix formatting, error messages --- .../jdbc/dialect/AuroraMysqlDialect.java | 2 +- .../amazon/jdbc/dialect/AuroraPgDialect.java | 6 +- .../amazon/jdbc/dialect/DialectManager.java | 13 ++-- .../jdbc/dialect/GlobalAuroraPgDialect.java | 3 +- .../jdbc/dialect/MultiAzClusterPgDialect.java | 1 + .../amazon/jdbc/dialect/RdsPgDialect.java | 4 +- .../jdbc/dialect/TopologyQueryHostSpec.java | 61 ------------------- .../hostlistprovider/AuroraTopologyUtils.java | 4 +- .../GlobalAuroraHostListProvider.java | 18 +++--- .../GlobalAuroraTopologyUtils.java | 11 ++-- .../MultiAzTopologyUtils.java | 5 +- .../hostlistprovider/RdsHostListProvider.java | 4 +- .../jdbc/hostlistprovider/TopologyUtils.java | 13 ++-- .../ClusterTopologyMonitorImpl.java | 26 ++++---- .../GlobalAuroraTopologyMonitor.java | 4 +- ...onitoringGlobalAuroraHostListProvider.java | 15 +++-- ..._advanced_jdbc_wrapper_messages.properties | 17 +++++- 17 files changed, 83 insertions(+), 124 deletions(-) delete mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 532c58343..a24862a4a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -49,7 +49,7 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, protected static final String BG_TOPOLOGY_EXISTS_QUERY = "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; - protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; + protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; @Override public boolean isDialect(final Connection connection) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 6c605ce16..41ff5d028 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -23,7 +23,6 @@ import java.util.Arrays; import java.util.List; import java.util.logging.Logger; -import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; @@ -31,6 +30,7 @@ import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringRdsHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; import software.amazon.jdbc.util.DriverInfo; +import software.amazon.jdbc.util.Messages; public class AuroraPgDialect extends PgDialect implements TopologyDialect, AuroraLimitlessDialect, BlueGreenDialect { @@ -63,7 +63,7 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror "SELECT 'pg_catalog.get_blue_green_fast_switchover_metadata'::regproc"; protected static final String BG_STATUS_QUERY = "SELECT * FROM " - + "pg_catalog.get_blue_green_fast_switchover_metadata('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; + + "pg_catalog.get_blue_green_fast_switchover_metadata('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; private static final Logger LOGGER = Logger.getLogger(AuroraPgDialect.class.getName()); @@ -78,7 +78,7 @@ public boolean isDialect(final Connection connection) { ResultSet rs = stmt.executeQuery(AURORA_UTILS_EXIST_QUERY)) { if (rs.next()) { final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); - LOGGER.finest(() -> String.format("auroraUtils: %b", auroraUtils)); + LOGGER.finest(Messages.get("AuroraPgDialect.auroraUtils", new Object[] {auroraUtils})); if (auroraUtils) { hasExtensions = true; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java index 73971216d..33869b784 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java @@ -140,7 +140,7 @@ public Dialect getDialect( String host = url; final List hosts = this.connectionUrlParser.getHostsFromConnectionUrl( - url, true, pluginService::getHostSpecBuilder); + url, true, pluginService::getHostSpecBuilder); if (!Utils.isNullOrEmpty(hosts)) { host = hosts.get(0).getHost(); } @@ -270,9 +270,12 @@ public Dialect getDialect( } private void logCurrentDialect() { - LOGGER.finest(() -> String.format("Current dialect: %s, %s, canUpdate: %b", - this.dialectCode, - this.dialect == null ? "" : this.dialect, - this.canUpdate)); + LOGGER.finest(Messages.get( + "DialectManager.currentDialect", + new Object[] { + this.dialectCode, + this.dialect == null ? "" : this.dialect, + this.canUpdate + })); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java index 5a623e714..3384461eb 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java @@ -28,6 +28,7 @@ import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.monitoring.MonitoringGlobalAuroraHostListProvider; import software.amazon.jdbc.plugin.failover2.FailoverConnectionPlugin; +import software.amazon.jdbc.util.Messages; public class GlobalAuroraPgDialect extends AuroraPgDialect implements GlobalAuroraTopologyDialect { @@ -56,7 +57,7 @@ public boolean isDialect(final Connection connection) { } final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); - LOGGER.finest(() -> String.format("auroraUtils: %b", auroraUtils)); + LOGGER.finest(Messages.get("AuroraPgDialect.auroraUtils", new Object[] {auroraUtils})); if (!auroraUtils) { return false; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 6547ea339..3716cefe0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -88,6 +88,7 @@ public HostListProviderSupplier getHostListProviderSupplier() { if (pluginService.isPluginInUse(FailoverConnectionPlugin.class)) { return new MonitoringRdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); } + return new RdsHostListProvider(topologyUtils, properties, initialUrl, servicesContainer); }; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java index ec9edb35d..79568d78f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.logging.Logger; import software.amazon.jdbc.util.DriverInfo; +import software.amazon.jdbc.util.Messages; /** * Suitable for the following AWS PG configurations. @@ -60,7 +61,8 @@ public boolean isDialect(final Connection connection) { while (rs.next()) { final boolean rdsTools = rs.getBoolean("rds_tools"); final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); - LOGGER.finest(() -> String.format("rdsTools: %b, auroraUtils: %b", rdsTools, auroraUtils)); + LOGGER.finest( + Messages.get("RdsPgDialect.rdsToolsAuroraUtils", new Object[] {rdsTools, auroraUtils})); if (rdsTools && !auroraUtils) { return true; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java deleted file mode 100644 index b4cb7a2c3..000000000 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyQueryHostSpec.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 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. - * 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 software.amazon.jdbc.dialect; - -import java.sql.Timestamp; -import org.checkerframework.checker.nullness.qual.Nullable; - -public class TopologyQueryHostSpec { - private final String instanceId; - private final boolean isWriter; - private final long weight; - private final Timestamp lastUpdateTime; - private final @Nullable String region; - - public TopologyQueryHostSpec(String instanceId, boolean isWriter, long weight, Timestamp lastUpdateTime) { - this(instanceId, isWriter, weight, lastUpdateTime, null); - } - - public TopologyQueryHostSpec( - String instanceId, boolean isWriter, long weight, Timestamp lastUpdateTime, @Nullable String region) { - this.instanceId = instanceId; - this.isWriter = isWriter; - this.weight = weight; - this.lastUpdateTime = lastUpdateTime; - this.region = region; - } - - public String getInstanceId() { - return instanceId; - } - - public boolean isWriter() { - return isWriter; - } - - public long getWeight() { - return weight; - } - - public Timestamp getLastUpdateTime() { - return lastUpdateTime; - } - - public @Nullable String getRegion() { - return this.region; - } -} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java index 79229e9cc..c8f84126b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -26,11 +26,9 @@ import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; -import software.amazon.jdbc.HostRole; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; @@ -52,7 +50,7 @@ public AuroraTopologyUtils(TopologyDialect dialect, HostSpecBuilder hostSpecBuil hosts.add(createHost(rs, initialHostSpec, hostTemplate)); } catch (Exception e) { LOGGER.finest( - Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); return null; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java index d5f09dec6..594a6fd2d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java @@ -30,6 +30,7 @@ import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; @@ -58,7 +59,8 @@ public class GlobalAuroraHostListProvider extends RdsHostListProvider { } public GlobalAuroraHostListProvider( - GlobalAuroraTopologyUtils topologyUtils, Properties properties, String originalUrl, FullServicesContainer servicesContainer) { + GlobalAuroraTopologyUtils topologyUtils, Properties properties, String originalUrl, + FullServicesContainer servicesContainer) { super(topologyUtils, properties, originalUrl, servicesContainer); this.topologyUtils = topologyUtils; } @@ -69,7 +71,7 @@ protected void initSettings() throws SQLException { String templates = GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); if (StringUtils.isNullOrEmpty(templates)) { - throw new SQLException("Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database."); + throw new SQLException(Messages.get("GlobalAuroraHostListProvider.globalClusterInstanceHostPatternsRequired")); } HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); @@ -81,11 +83,13 @@ protected void initSettings() throws SQLException { this.validateHostPatternSetting(v.getValue2().getHost()); return v.getValue2(); })); - LOGGER.finest(() -> "Recognized GDB instance template patterns:\n" - + this.instanceTemplatesByRegion.entrySet().stream() - .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) - .collect(Collectors.joining("\n")) - ); + + // TODO: utility to convert Map to log string + LOGGER.finest(Messages.get("GlobalAuroraHostListProvider.detectedGdbPatterns", new Object[] { + this.instanceTemplatesByRegion.entrySet().stream() + .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) + .collect(Collectors.joining("\n")) + })); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index 528e8b94c..003860cce 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -32,7 +32,6 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.GlobalAuroraTopologyDialect; -import software.amazon.jdbc.dialect.TopologyDialect; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; @@ -54,7 +53,7 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { if (resultSet.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); + LOGGER.finest(Messages.get("TopologyUtils.unexpectedTopologyQueryColumnCount")); return null; } @@ -79,7 +78,7 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu hosts.add(createHost(rs, initialHostSpec, instanceTemplatesByRegion)); } catch (Exception e) { LOGGER.finest( - Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); return null; } } @@ -102,10 +101,12 @@ protected HostSpec createHost( final HostSpec clusterInstanceTemplateForRegion = instanceTemplatesByRegion.get(awsRegion); if (clusterInstanceTemplateForRegion == null) { - throw new SQLException("Can't find cluster template for region " + awsRegion); + throw new SQLException( + Messages.get("GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); } - return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, clusterInstanceTemplateForRegion); + return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, + clusterInstanceTemplateForRegion); } public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 1f0494fa7..715ddbf40 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -23,15 +23,12 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.MultiAzClusterDialect; -import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.dialect.TopologyQueryHostSpec; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; @@ -59,7 +56,7 @@ public MultiAzTopologyUtils(MultiAzClusterDialect dialect, HostSpecBuilder hostS hosts.add(createHost(rs, initialHostSpec, hostTemplate, writerId)); } catch (Exception e) { LOGGER.finest( - Messages.get("AuroraDialectUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); return null; } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 6baf6ed01..12b2c70a1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -164,9 +164,9 @@ protected void initSettings() throws SQLException { * cached copy of topology is returned if it's not yet outdated (controlled by {@link * #refreshRateNano}). * - * @param conn A connection to database to fetch the latest topology, if needed. + * @param conn A connection to database to fetch the latest topology, if needed. * @param forceUpdate If true, it forces a service to ignore cached copy of topology and to fetch - * a fresh one. + * a fresh one. * @return a list of hosts that describes cluster topology. A writer is always at position 0. * Returns an empty list if isn't available or is invalid (doesn't contain a writer). * @throws SQLException if errors occurred while retrieving the topology. diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index 8d24d3614..b926cee6a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -26,7 +26,6 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.concurrent.Executor; import java.util.logging.Logger; import java.util.stream.Collectors; @@ -35,12 +34,10 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.TopologyDialect; -import software.amazon.jdbc.dialect.TopologyQueryHostSpec; import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.SynchronousExecutor; -import software.amazon.jdbc.util.Utils; public abstract class TopologyUtils { private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); @@ -64,7 +61,7 @@ public TopologyUtils( final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { if (resultSet.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. - LOGGER.finest(Messages.get("AuroraDialectUtils.unexpectedTopologyQueryColumnCount")); + LOGGER.finest(Messages.get("TopologyUtils.unexpectedTopologyQueryColumnCount")); return null; } @@ -157,11 +154,11 @@ public HostSpec createHost( * *

Database types handle these identifiers differently: * - Aurora: Uses the instance name as both instanceId and instanceName - * Example: "test-instance-1" for both values + * Example: "test-instance-1" for both values * - RDS Cluster: Uses distinct values for instanceId and instanceName - * Example: - * instanceId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" - * instanceName: "test-multiaz-instance-1" + * Example: + * instanceId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" + * instanceName: "test-multiaz-instance-1" */ public @Nullable Pair getInstanceId(final Connection connection) { try { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index cea64fa52..0271b2e1e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -216,9 +216,8 @@ protected List waitTillTopologyGetsUpdated(final long timeoutMs) throw } if (System.nanoTime() >= end) { - throw new TimeoutException(Messages.get( - "ClusterTopologyMonitorImpl.topologyNotUpdated", - new Object[]{timeoutMs})); + throw new TimeoutException( + Messages.get("ClusterTopologyMonitorImpl.topologyNotUpdated", new Object[] {timeoutMs})); } return latestHosts; @@ -255,7 +254,7 @@ public void monitor() throws Exception { try { LOGGER.finest(() -> Messages.get( "ClusterTopologyMonitorImpl.startMonitoringThread", - new Object[]{this.clusterId, this.initialHostSpec.getHost()})); + new Object[] {this.clusterId, this.initialHostSpec.getHost()})); while (!this.stop.get() && !Thread.currentThread().isInterrupted()) { this.lastActivityTimestampNanos.set(System.nanoTime()); @@ -318,7 +317,7 @@ public void monitor() throws Exception { LOGGER.finest( Messages.get( "ClusterTopologyMonitorImpl.writerPickedUpFromNodeMonitors", - new Object[]{writerConnectionHostSpec})); + new Object[] {writerConnectionHostSpec})); this.closeConnection(this.monitoringConnection.get()); this.monitoringConnection.set(writerConnection); @@ -418,7 +417,7 @@ public void monitor() throws Exception { Level.FINEST, Messages.get( "ClusterTopologyMonitorImpl.exceptionDuringMonitoringStop", - new Object[]{this.initialHostSpec.getHost()}), + new Object[] {this.initialHostSpec.getHost()}), ex); } @@ -433,7 +432,7 @@ public void monitor() throws Exception { LOGGER.finest(() -> Messages.get( "ClusterTopologyMonitorImpl.stopMonitoringThread", - new Object[]{this.initialHostSpec.getHost()})); + new Object[] {this.initialHostSpec.getHost()})); } } @@ -504,7 +503,7 @@ protected List openAnyConnectionAndUpdateTopology() { if (this.monitoringConnection.compareAndSet(null, conn)) { LOGGER.finest(() -> Messages.get( "ClusterTopologyMonitorImpl.openedMonitoringConnection", - new Object[]{this.initialHostSpec.getHost()})); + new Object[] {this.initialHostSpec.getHost()})); try { if (this.topologyUtils.isWriterInstance(this.monitoringConnection.get())) { @@ -516,7 +515,7 @@ protected List openAnyConnectionAndUpdateTopology() { LOGGER.finest( Messages.get( "ClusterTopologyMonitorImpl.writerMonitoringConnection", - new Object[]{this.writerHostSpec.get().getHost()})); + new Object[] {this.writerHostSpec.get().getHost()})); } else { final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); // TODO: this code isn't quite right when compared to main-3.x @@ -524,12 +523,13 @@ protected List openAnyConnectionAndUpdateTopology() { HostSpec hostTemplate = this.getClusterInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); HostSpec writerHost = - this.topologyUtils.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, hostTemplate); + this.topologyUtils.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, + this.initialHostSpec, hostTemplate); this.writerHostSpec.set(writerHost); LOGGER.finest( Messages.get( "ClusterTopologyMonitorImpl.writerMonitoringConnection", - new Object[]{this.writerHostSpec.get().getHost()})); + new Object[] {this.writerHostSpec.get().getHost()})); } } } @@ -615,7 +615,7 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { } return hosts; } catch (SQLException ex) { - LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.errorFetchingTopology", new Object[]{ex})); + LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.errorFetchingTopology", new Object[] {ex})); } return null; } @@ -714,7 +714,7 @@ public void run() { } else { // writer connection is successfully set to writerConnection - LOGGER.fine(Messages.get("NodeMonitoringThread.detectedWriter", new Object[]{hostSpec.getUrl()})); + LOGGER.fine(Messages.get("NodeMonitoringThread.detectedWriter", new Object[] {hostSpec.getUrl()})); // When nodeThreadsWriterConnection and nodeThreadsWriterHostSpec are both set, the topology monitor may // set ignoreNewTopologyRequestsEndTimeNano, in which case other threads will use the cached topology // for the ignore duration, so we need to update the topology before setting nodeThreadsWriterHostSpec. diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java index fa8680060..9c135312e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java @@ -25,6 +25,7 @@ import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.StringUtils; @@ -61,7 +62,8 @@ protected HostSpec getClusterInstanceTemplate(String instanceId, Connection conn if (!StringUtils.isNullOrEmpty(region)) { final HostSpec clusterInstanceTemplateForRegion = this.instanceTemplatesByRegion.get(region); if (clusterInstanceTemplateForRegion == null) { - throw new SQLException("Can't find cluster template for region " + region); + throw new SQLException( + Messages.get("GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {region})); } return clusterInstanceTemplateForRegion; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java index 84395e0fc..b42cf80f0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java @@ -32,6 +32,7 @@ import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; @@ -46,7 +47,7 @@ public class MonitoringGlobalAuroraHostListProvider extends MonitoringRdsHostLis protected final GlobalAuroraTopologyUtils topologyUtils; static { - // Register property definition in AuroraGlobalDbHostListProvider class. It's not a mistake. + // Intentionally register property definition in GlobalAuroraHostListProvider class. PropertyDefinition.registerPluginProperties(GlobalAuroraHostListProvider.class); } @@ -66,7 +67,7 @@ protected void initSettings() throws SQLException { // TODO: check if we have other places that parse into string-HostSpec maps, consider refactoring String templates = GlobalAuroraHostListProvider.GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); if (StringUtils.isNullOrEmpty(templates)) { - throw new SQLException("Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database."); + throw new SQLException(Messages.get("MonitoringGlobalAuroraHostListProvider.globalHostPatternsRequired")); } HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); @@ -78,10 +79,12 @@ protected void initSettings() throws SQLException { this.validateHostPatternSetting(v.getValue2().getHost()); return v.getValue2(); })); - LOGGER.finest(() -> "Recognized GDB instance template patterns:\n" - + this.instanceTemplatesByRegion.entrySet().stream() - .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) - .collect(Collectors.joining("\n")) + LOGGER.finest(Messages.get( + "GlobalAuroraHostListProvider.detectedGdbPatterns", new Object[] { + this.instanceTemplatesByRegion.entrySet().stream() + .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) + .collect(Collectors.joining("\n")) + }) ); } diff --git a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties index 2cdc48935..5a0e509fe 100644 --- a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties +++ b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties @@ -27,8 +27,7 @@ AdfsCredentialsProviderFactory.signOnPagePostActionRequestFailed=ADFS SignOn Pag AdfsCredentialsProviderFactory.signOnPageRequestFailed=ADFS SignOn Page Request Failed with HTTP status ''{0}'', reason phrase ''{1}'', and response ''{2}'' AdfsCredentialsProviderFactory.signOnPageUrl=ADFS SignOn URL: ''{0}'' -AuroraDialectUtils.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} -AuroraDialectUtils.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. +AuroraPgDialect.auroraUtils=auroraUtils: {0} AuthenticationToken.useCachedToken=Use cached authentication token = ''{0}'' AuthenticationToken.generatedNewToken=Generated new authentication token = ''{0}'' @@ -37,10 +36,11 @@ AuthenticationToken.javaSdkNotInClasspath=Required dependency 'AWS Java SDK RDS RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRDSProxy=An RDS Proxy url can''t be used as the 'clusterInstanceHostPattern' configuration setting. RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRdsCustom=A custom RDS url can''t be used as the 'clusterInstanceHostPattern' configuration setting. RdsHostListProvider.invalidPattern=Invalid value for the 'clusterInstanceHostPattern' configuration setting - the host pattern must contain a '?' character as a placeholder for the DB instance identifiers of the instances in the cluster. -RdsHostListProvider.suggestedClusterId=ClusterId ''{0}'' is suggested for url ''{1}''. RdsHostListProvider.parsedListEmpty=Can''t parse connection string: ''{0}'' RdsHostListProvider.errorIdentifyConnection=An error occurred while obtaining the connection's host ID. +RdsPgDialect.rdsToolsAuroraUtils=rdsTools: {0}, auroraUtils: {1} + AwsSdk.unsupportedRegion=Unsupported AWS region ''{0}''. For supported regions please read https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html AwsSecretsManagerConnectionPlugin.endpointOverrideMisconfigured=The provided endpoint is invalid and could not be used to create a URI: `{0}`. @@ -134,6 +134,8 @@ DefaultConnectionPlugin.executingMethod=Executing method: ''{0}'' DefaultConnectionPlugin.noHostsAvailable=The default connection plugin received an empty host list from the plugin service. DefaultConnectionPlugin.unknownRoleRequested=A HostSpec with a role of HostRole.UNKNOWN was requested via getHostSpecByStrategy. The requested role must be either HostRole.WRITER or HostRole.READER +DialectManager.currentDialect=Current dialect: {0}, {1}, canUpdate: {2} + Driver.nullUrl=Url is null. Driver.alreadyRegistered=Driver is already registered. It can only be registered once. Driver.missingDriver=Can''t find the target driver for ''{0}''. Please ensure the target driver is in the classpath and is registered. Here is the list of registered drivers in the classpath: {1} @@ -183,6 +185,11 @@ Failover.skipFailoverOnInterruptedThread=Do not start failover since the current FederatedAuthPlugin.unableToDetermineRegion=Unable to determine connection region. If you are using a non-standard RDS URL, please set the ''{0}'' property. +GlobalAuroraHostListProvider.globalClusterInstanceHostPatternsRequired=Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database. +GlobalAuroraHostListProvider.detectedGdbPatterns=Detected GDB instance template patterns:\n{0} + +GlobalAuroraTopologyMonitor.cannotFindRegionTemplate=Cannot find cluster template for region {0}. + HostAvailabilityStrategy.invalidMaxRetries=Invalid value of {0} for configuration parameter `hostAvailabilityStrategyMaxRetries`. It must be an integer greater than 1. HostAvailabilityStrategy.invalidInitialBackoffTime=Invalid value of {0} for configuration parameter `hostAvailabilityStrategyInitialBackoffTime`. It must be an integer greater than 1. @@ -258,6 +265,8 @@ HostMonitorServiceImpl.emptyAliasSet=Empty alias set passed for ''{0}''. Set sho HostResponseTimeServiceImpl.errorStartingMonitor=An error occurred while starting a response time monitor for ''{0}'': {1} +MonitoringGlobalAuroraHostListProvider.globalHostPatternsRequired=Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database. + MonitorServiceImpl.checkingMonitors=Checking monitors for errors... MonitorServiceImpl.monitorClassMismatch=The monitor stored at ''{0}'' did not have the expected type. The expected type was ''{1}'', but the monitor ''{2}'' had a type of ''{3}''. MonitorServiceImpl.monitorStuck=Monitor ''{0}'' has not been updated within the inactive timeout of {1} milliseconds. The monitor will be stopped. @@ -359,8 +368,10 @@ MysqlConnectorJDriverHelper.canNotRegister=Can''t register driver com.mysql.cj. TopologyUtils.errorGettingHostRole=An error occurred while obtaining the connected host's role. This could occur if the connection is broken or if you are not connected to an Aurora database. TopologyUtils.errorGettingNetworkTimeout=An error occurred while getting the connection network timeout: {0} +TopologyUtils.errorProcessingQueryResults=An error occurred while processing the results from the topology query: {0} TopologyUtils.invalidQuery=An error occurred while attempting to obtain the topology because the topology query was invalid. Please ensure you are connecting to an Aurora or RDS cluster. TopologyUtils.invalidTopology=The topology query returned an invalid topology - no writer instance detected. +TopologyUtils.unexpectedTopologyQueryColumnCount=The topology query returned a result with 0 columns. This may occur if the topology query is executed when the server is failing over. MariadbDriverHelper.canNotRegister=Can''t register driver org.mariadb.jdbc.Driver. From 0a537b30e6101f5f960cf0e5ce00527fb3c05ed1 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 15:19:00 -0800 Subject: [PATCH 29/46] Cleanup comments --- .../amazon/jdbc/PartialPluginService.java | 2 +- .../amazon/jdbc/PluginServiceImpl.java | 2 +- .../jdbc/dialect/AuroraMysqlDialect.java | 4 +- .../amazon/jdbc/dialect/DialectManager.java | 16 +- .../dialect/GlobalAuroraTopologyDialect.java | 3 - .../dialect/MultiAzClusterMysqlDialect.java | 11 +- .../jdbc/dialect/MultiAzClusterPgDialect.java | 10 +- .../amazon/jdbc/dialect/RdsMysqlDialect.java | 2 +- .../amazon/jdbc/dialect/RdsPgDialect.java | 3 +- .../amazon/jdbc/dialect/TopologyDialect.java | 6 - .../hostlistprovider/AuroraTopologyUtils.java | 39 ++--- .../ConnectionStringHostListProvider.java | 4 +- .../DynamicHostListProvider.java | 6 +- .../GlobalAuroraHostListProvider.java | 33 +--- .../GlobalAuroraTopologyUtils.java | 71 +++++--- .../MultiAzTopologyUtils.java | 53 +++--- .../hostlistprovider/RdsHostListProvider.java | 48 ++---- .../StaticHostListProvider.java | 4 +- .../jdbc/hostlistprovider/TopologyUtils.java | 35 ++-- .../ClusterTopologyMonitorImpl.java | 153 ++++++++---------- .../GlobalAuroraTopologyMonitor.java | 15 +- ...onitoringGlobalAuroraHostListProvider.java | 35 +--- .../MonitoringRdsHostListProvider.java | 4 +- .../bluegreen/BlueGreenInterimStatus.java | 4 +- .../ClusterAwareWriterFailoverHandler.java | 2 +- .../failover/FailoverConnectionPlugin.java | 4 +- .../failover2/FailoverConnectionPlugin.java | 8 +- .../limitless/LimitlessRouterMonitor.java | 2 +- .../ReadWriteSplittingPlugin.java | 2 +- .../plugin/staledns/AuroraStaleDnsHelper.java | 4 +- .../software/amazon/jdbc/util/LogUtils.java | 55 +++++++ .../java/software/amazon/jdbc/util/Utils.java | 24 --- ..._advanced_jdbc_wrapper_messages.properties | 6 +- 33 files changed, 320 insertions(+), 350 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/util/LogUtils.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 56ff38406..9828a2d25 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -159,7 +159,7 @@ public HostSpec getCurrentHostSpec() { Messages.get("PluginServiceImpl.currentHostNotAllowed", new Object[] { currentHostSpec == null ? "" : currentHostSpec.getHostAndPort(), - Utils.logTopology(allowedHosts, "")}) + LogUtils.logTopology(allowedHosts, "")}) ); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 0428e1b12..cd6738b1a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -193,7 +193,7 @@ public HostSpec getCurrentHostSpec() { Messages.get("PluginServiceImpl.currentHostNotAllowed", new Object[] { currentHostSpec == null ? "" : currentHostSpec.getHostAndPort(), - Utils.logTopology(allowedHosts, "")}) + LogUtils.logTopology(allowedHosts, "")}) ); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index a24862a4a..deb6c7af8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -22,7 +22,6 @@ import java.sql.Statement; import java.util.Collections; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; @@ -37,7 +36,7 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, "SELECT SERVER_ID, CASE WHEN SESSION_ID = 'MASTER_SESSION_ID' THEN TRUE ELSE FALSE END, " + "CPU, REPLICA_LAG_IN_MILLISECONDS, LAST_UPDATE_TIMESTAMP " + "FROM information_schema.replica_host_status " - // filter out instances that haven't been updated in the last 5 minutes + // filter out instances that have not been updated in the last 5 minutes + "WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID' "; protected static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; @@ -56,7 +55,6 @@ public boolean isDialect(final Connection connection) { try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(AURORA_VERSION_EXISTS_QUERY)) { if (rs.next()) { - // If variable with such name is presented then it means it's an Aurora cluster return true; } } catch (final SQLException ex) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java index 33869b784..ed7f4e71e 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectManager.java @@ -75,7 +75,7 @@ public class DialectManager implements DialectProvider { */ protected static final long ENDPOINT_CACHE_EXPIRATION = TimeUnit.HOURS.toNanos(24); - // Map of host name, or url, by dialect code. + // Keys are host names or URLs, values are dialect codes. protected static final CacheMap knownEndpointDialects = new CacheMap<>(); private final RdsUtils rdsHelper = new RdsUtils(); @@ -129,8 +129,7 @@ public Dialect getDialect( this.logCurrentDialect(); return userDialect; } else { - throw new SQLException( - Messages.get("DialectManager.unknownDialectCode", new Object[] {dialectCode})); + throw new SQLException(Messages.get("DialectManager.unknownDialectCode", new Object[] {dialectCode})); } } @@ -238,9 +237,10 @@ public Dialect getDialect( for (String dialectCandidateCode : dialectCandidates) { Dialect dialectCandidate = knownDialectsByCode.get(dialectCandidateCode); if (dialectCandidate == null) { - throw new SQLException( - Messages.get("DialectManager.unknownDialectCode", new Object[] {dialectCandidateCode})); + throw new SQLException(Messages.get( + "DialectManager.unknownDialectCode", new Object[] {dialectCandidateCode})); } + boolean isDialect = dialectCandidate.isDialect(connection); if (isDialect) { this.canUpdate = false; @@ -272,10 +272,6 @@ public Dialect getDialect( private void logCurrentDialect() { LOGGER.finest(Messages.get( "DialectManager.currentDialect", - new Object[] { - this.dialectCode, - this.dialect == null ? "" : this.dialect, - this.canUpdate - })); + new Object[] {this.dialectCode, this.dialect == null ? "" : this.dialect, this.canUpdate})); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java index 1febb80a7..11db48dff 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraTopologyDialect.java @@ -16,9 +16,6 @@ package software.amazon.jdbc.dialect; -import java.sql.Connection; -import java.sql.SQLException; - public interface GlobalAuroraTopologyDialect extends TopologyDialect { String getRegionByInstanceIdQuery(); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 2da87edba..6087a6ec7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -26,7 +26,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.PluginService; -import software.amazon.jdbc.hostlistprovider.AuroraTopologyUtils; import software.amazon.jdbc.hostlistprovider.MultiAzTopologyUtils; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider; import software.amazon.jdbc.hostlistprovider.TopologyUtils; @@ -45,12 +44,12 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzC + " table_schema = 'mysql' AND table_name = 'rds_topology'"; protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; - // The query return nodeId and nodeName. + // This query returns both instanceId and instanceName. // For example: "1845128080", "test-multiaz-instance-1" private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING_INDEX(endpoint, '.', 1)" + " FROM mysql.rds_topology" + " WHERE id = @@server_id"; - // For reader instances, the query returns a writer instance ID. For a writer instance, the query returns no data. + // For reader instances, this query returns a writer instance ID. For a writer instance, this query returns no data. protected static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; protected static final String WRITER_ID_QUERY_COLUMN_NAME = "Source_Server_Id"; protected static final String IS_READER_QUERY = "SELECT @@read_only"; @@ -82,14 +81,14 @@ public boolean isDialect(final Connection connection) { if (!rs.next()) { return false; } - final String reportHost = rs.getString(2); // get variable value; expected value is IP address + + final String reportHost = rs.getString(2); // Expected value is an IP address return !StringUtils.isNullOrEmpty(reportHost); } } catch (final SQLException ex) { - // ignore + return false; } - return false; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 3716cefe0..6750a3efd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -21,7 +21,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.exceptions.ExceptionHandler; import software.amazon.jdbc.exceptions.MultiAzDbClusterPgExceptionHandler; @@ -39,14 +38,14 @@ public class MultiAzClusterPgDialect extends PgDialect implements MultiAzCluster protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - // The query return nodeId and nodeName. + // This query returns both instanceId and instanceName. // For example: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ", "test-multiaz-instance-1" private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + " FROM rds_tools.show_topology()" + " WHERE id OPERATOR(pg_catalog.=) rds_tools.dbi_resource_id()"; - // For reader instances, the query should return a writer instance ID. - // For a writer instance, the query should return no data. + // For reader instances, this query should return a writer instance ID. + // For a writer instance, this query should return no data. protected static final String WRITER_ID_QUERY = "SELECT multi_az_db_cluster_source_dbi_resource_id FROM rds_tools.multi_az_db_cluster_source_dbi_resource_id()" + " WHERE multi_az_db_cluster_source_dbi_resource_id OPERATOR(pg_catalog.!=)" @@ -62,9 +61,8 @@ public boolean isDialect(final Connection connection) { ResultSet rs = stmt.executeQuery(IS_RDS_CLUSTER_QUERY)) { return rs.next() && rs.getString(1) != null; } catch (final SQLException ex) { - // ignore + return false; } - return false; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java index 6b6e800b5..2a0b1ed29 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java @@ -76,7 +76,7 @@ public boolean isDialect(final Connection connection) { return false; } - final String reportHost = rs.getString(2); // get variable value; expected empty value + final String reportHost = rs.getString(2); // An empty value is expected return StringUtils.isNullOrEmpty(reportHost); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java index 79568d78f..3ccad1a0f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java @@ -61,8 +61,7 @@ public boolean isDialect(final Connection connection) { while (rs.next()) { final boolean rdsTools = rs.getBoolean("rds_tools"); final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); - LOGGER.finest( - Messages.get("RdsPgDialect.rdsToolsAuroraUtils", new Object[] {rdsTools, auroraUtils})); + LOGGER.finest(Messages.get("RdsPgDialect.rdsToolsAuroraUtils", new Object[] {rdsTools, auroraUtils})); if (rdsTools && !auroraUtils) { return true; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java index 84ac7ae3a..e7aa0f4d2 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/TopologyDialect.java @@ -16,12 +16,6 @@ package software.amazon.jdbc.dialect; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.List; -import org.checkerframework.checker.nullness.qual.Nullable; - public interface TopologyDialect extends Dialect { String getTopologyQuery(); diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java index c8f84126b..f62415ff9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -23,7 +23,9 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; @@ -41,29 +43,29 @@ public AuroraTopologyUtils(TopologyDialect dialect, HostSpecBuilder hostSpecBuil @Override protected @Nullable List getHosts( - Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException { - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - List hosts = new ArrayList<>(); + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException { + // Data in the result set is ordered by last update time, so the latest records are last. + // We add hosts to a map to ensure newer records are not overwritten by older ones. + Map hostsMap = new HashMap<>(); while (rs.next()) { try { - hosts.add(createHost(rs, initialHostSpec, hostTemplate)); + HostSpec host = createHost(rs, initialHostSpec, instanceTemplate); + hostsMap.put(host.getHost(), host); } catch (Exception e) { - LOGGER.finest( - Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); + LOGGER.finest(Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); return null; } } - return hosts; + return new ArrayList<>(hostsMap.values()); } @Override public boolean isWriterInstance(final Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { - if (resultSet.next()) { - return !StringUtils.isNullOrEmpty(resultSet.getString(1)); + try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (rs.next()) { + return !StringUtils.isNullOrEmpty(rs.getString(1)); } } } @@ -71,14 +73,13 @@ public boolean isWriterInstance(final Connection connection) throws SQLException return false; } - protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException { - - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), CPU utilization, node lag in time. + protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException { + // According to the topology query the result set should contain 4 columns: + // instance ID, 1/0 (writer/reader), CPU utilization, instance lag in time. String hostName = rs.getString(1); final boolean isWriter = rs.getBoolean(2); final double cpuUtilization = rs.getDouble(3); - final double nodeLag = rs.getDouble(4); + final double instanceLag = rs.getDouble(4); Timestamp lastUpdateTime; try { lastUpdateTime = rs.getTimestamp(5); @@ -86,9 +87,9 @@ protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec h lastUpdateTime = Timestamp.from(Instant.now()); } - // Calculate weight based on node lag in time and CPU utilization. - final long weight = Math.round(nodeLag) * 100L + Math.round(cpuUtilization); + // Calculate weight based on instance lag in time and CPU utilization. + final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); - return createHost(hostName, hostName, isWriter, weight, lastUpdateTime, initialHostSpec, hostTemplate); + return createHost(hostName, hostName, isWriter, weight, lastUpdateTime, initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java index bbf3209ed..426ea3963 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/ConnectionStringHostListProvider.java @@ -35,7 +35,6 @@ public class ConnectionStringHostListProvider implements StaticHostListProvider private static final Logger LOGGER = Logger.getLogger(ConnectionStringHostListProvider.class.getName()); final List hostList = new ArrayList<>(); - Properties properties; private boolean isInitialized = false; private final boolean isSingleWriterConnectionString; private final ConnectionUrlParser connectionUrlParser; @@ -74,11 +73,12 @@ private void init() throws SQLException { } this.hostList.addAll( this.connectionUrlParser.getHostsFromConnectionUrl(this.initialUrl, this.isSingleWriterConnectionString, - () -> this.hostListProviderService.getHostSpecBuilder())); + this.hostListProviderService::getHostSpecBuilder)); if (this.hostList.isEmpty()) { throw new SQLException(Messages.get("ConnectionStringHostListProvider.parsedListEmpty", new Object[] {this.initialUrl})); } + this.hostListProviderService.setInitialConnectionHostSpec(this.hostList.get(0)); this.isInitialized = true; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java index c4ef01aae..09d321c41 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/DynamicHostListProvider.java @@ -16,7 +16,7 @@ package software.amazon.jdbc.hostlistprovider; -// A marker interface for providers that can fetch a host list, and it changes depending on database status -// A good example of such provider would be DB cluster provider (Aurora DB clusters, patroni DB clusters, etc.) -// where cluster topology (nodes, their roles, their statuses) changes over time. +// A marker interface for providers that can fetch a host list reflecting the current database topology. +// Examples include providers for Aurora or Multi-AZ clusters, where the cluster topology, status, and instance roles +// change over time. public interface DynamicHostListProvider extends HostListProvider { } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java index 594a6fd2d..682ebb31f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraHostListProvider.java @@ -18,27 +18,18 @@ import java.sql.Connection; import java.sql.SQLException; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; -import java.util.stream.Collectors; import software.amazon.jdbc.AwsWrapperProperty; import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; -import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; -import software.amazon.jdbc.util.StringUtils; public class GlobalAuroraHostListProvider extends RdsHostListProvider { - static final Logger LOGGER = Logger.getLogger(GlobalAuroraHostListProvider.class.getName()); - public static final AwsWrapperProperty GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS = new AwsWrapperProperty( "globalClusterInstanceHostPatterns", @@ -69,27 +60,9 @@ public GlobalAuroraHostListProvider( protected void initSettings() throws SQLException { super.initSettings(); - String templates = GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); - if (StringUtils.isNullOrEmpty(templates)) { - throw new SQLException(Messages.get("GlobalAuroraHostListProvider.globalClusterInstanceHostPatternsRequired")); - } - - HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); - this.instanceTemplatesByRegion = Arrays.stream(templates.split(",")) - .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) - .collect(Collectors.toMap( - Pair::getValue1, - v -> { - this.validateHostPatternSetting(v.getValue2().getHost()); - return v.getValue2(); - })); - - // TODO: utility to convert Map to log string - LOGGER.finest(Messages.get("GlobalAuroraHostListProvider.detectedGdbPatterns", new Object[] { - this.instanceTemplatesByRegion.entrySet().stream() - .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) - .collect(Collectors.joining("\n")) - })); + String instanceTemplates = GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); + this.instanceTemplatesByRegion = + this.topologyUtils.parseInstanceTemplates(instanceTemplates, this::validateHostPatternSetting); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index 003860cce..e08d5877a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -25,14 +25,21 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; import java.util.logging.Logger; +import java.util.stream.Collectors; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.dialect.GlobalAuroraTopologyDialect; +import software.amazon.jdbc.util.ConnectionUrlParser; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.StringUtils; public class GlobalAuroraTopologyUtils extends AuroraTopologyUtils { @@ -50,14 +57,14 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu throws SQLException { int networkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - if (resultSet.getMetaData().getColumnCount() == 0) { + final ResultSet rs = stmt.executeQuery(this.dialect.getTopologyQuery())) { + if (rs.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. LOGGER.finest(Messages.get("TopologyUtils.unexpectedTopologyQueryColumnCount")); return null; } - return this.verifyWriter(this.getHosts(conn, resultSet, initialHostSpec, instanceTemplatesByRegion)); + return this.verifyWriter(this.getHosts(rs, initialHostSpec, instanceTemplatesByRegion)); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -68,29 +75,28 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu } protected @Nullable List getHosts( - Connection conn, ResultSet rs, HostSpec initialHostSpec, Map instanceTemplatesByRegion) - throws SQLException { - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - List hosts = new ArrayList<>(); + ResultSet rs, HostSpec initialHostSpec, Map instanceTemplatesByRegion) throws SQLException { + // Data in the result set is ordered by last update time, so the latest records are last. + // We add hosts to a map to ensure newer records are not overwritten by older ones. + Map hostsMap = new HashMap<>(); while (rs.next()) { try { - hosts.add(createHost(rs, initialHostSpec, instanceTemplatesByRegion)); + HostSpec host = createHost(rs, initialHostSpec, instanceTemplatesByRegion); + hostsMap.put(host.getHost(), host); } catch (Exception e) { - LOGGER.finest( - Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); + LOGGER.finest(Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[] {e.getMessage()})); return null; } } - return hosts; + return new ArrayList<>(hostsMap.values()); } protected HostSpec createHost( ResultSet rs, HostSpec initialHostSpec, Map instanceTemplatesByRegion) throws SQLException { - // According to the topology query the result set - // should contain 4 columns: node ID, 1/0 (writer/reader), node lag in time (msec), AWS region. + // According to the topology query the result set should contain 4 columns: + // instance ID, 1/0 (writer/reader), node lag in time (msec), AWS region. String hostName = rs.getString(1); final boolean isWriter = rs.getBoolean(2); final float nodeLag = rs.getFloat(3); @@ -99,22 +105,22 @@ protected HostSpec createHost( // Calculate weight based on node lag in time and CPU utilization. final long weight = Math.round(nodeLag) * 100L; - final HostSpec clusterInstanceTemplateForRegion = instanceTemplatesByRegion.get(awsRegion); - if (clusterInstanceTemplateForRegion == null) { - throw new SQLException( - Messages.get("GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); + final HostSpec instanceTemplate = instanceTemplatesByRegion.get(awsRegion); + if (instanceTemplate == null) { + throw new SQLException(Messages.get( + "GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); } return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, - clusterInstanceTemplateForRegion); + instanceTemplate); } public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { try (final PreparedStatement stmt = conn.prepareStatement(this.dialect.getRegionByInstanceIdQuery())) { stmt.setString(1, instanceId); - try (final ResultSet resultSet = stmt.executeQuery()) { - if (resultSet.next()) { - String awsRegion = resultSet.getString(1); + try (final ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + String awsRegion = rs.getString(1); return StringUtils.isNullOrEmpty(awsRegion) ? null : awsRegion; } } @@ -122,4 +128,25 @@ protected HostSpec createHost( return null; } + + public Map parseInstanceTemplates(String instanceTemplatesString, Consumer hostValidator) + throws SQLException { + if (StringUtils.isNullOrEmpty(instanceTemplatesString)) { + throw new SQLException(Messages.get("GlobalAuroraTopologyUtils.globalClusterInstanceHostPatternsRequired")); + } + + Map instanceTemplates = Arrays.stream(instanceTemplatesString.split(",")) + .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) + .collect(Collectors.toMap( + Pair::getValue1, + v -> { + hostValidator.accept(v.getValue2().getHost()); + return v.getValue2(); + })); + LOGGER.finest(Messages.get( + "GlobalAuroraTopologyUtils.detectedGdbPatterns", + new Object[] {LogUtils.toLogString(instanceTemplates)})); + + return instanceTemplates; + } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 715ddbf40..2ba81b980 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -23,7 +23,9 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.logging.Logger; import org.checkerframework.checker.nullness.qual.Nullable; import software.amazon.jdbc.HostSpec; @@ -44,75 +46,78 @@ public MultiAzTopologyUtils(MultiAzClusterDialect dialect, HostSpecBuilder hostS @Override protected @Nullable List getHosts( - Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException { String writerId = this.getWriterId(conn); - // Data is result set is ordered by last updated time so the latest records go last. - // When adding hosts to a map, the newer records replace the older ones. - List hosts = new ArrayList<>(); + // Data in the result set is ordered by last update time, so the latest records are last. + // We add hosts to a map to ensure newer records are not overwritten by older ones. + Map hostsMap = new HashMap<>(); while (rs.next()) { try { - hosts.add(createHost(rs, initialHostSpec, hostTemplate, writerId)); + HostSpec host = createHost(rs, initialHostSpec, instanceTemplate, writerId); + hostsMap.put(host.getHost(), host); } catch (Exception e) { - LOGGER.finest( - Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); + LOGGER.finest(Messages.get("TopologyUtils.errorProcessingQueryResults", new Object[]{e.getMessage()})); return null; } } - return hosts; + return new ArrayList<>(hostsMap.values()); } @Override public boolean isWriterInstance(final Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { - if (resultSet.next()) { - String instanceId = resultSet.getString(this.dialect.getWriterIdColumnName()); + try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (rs.next()) { + String instanceId = rs.getString(this.dialect.getWriterIdColumnName()); // The writer ID is only returned when connected to a reader, so if the query does not return a value, it // means we are connected to a writer. return StringUtils.isNullOrEmpty(instanceId); } } } + return false; } protected @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { - try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getWriterIdQuery())) { - if (resultSet.next()) { - String writerId = resultSet.getString(this.dialect.getWriterIdColumnName()); + try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { + if (rs.next()) { + String writerId = rs.getString(this.dialect.getWriterIdColumnName()); if (!StringUtils.isNullOrEmpty(writerId)) { return writerId; } } } - // Replica status doesn't exist, which means that this instance is a writer. We execute instanceIdQuery to get the - // ID of this writer instance. - try (final ResultSet resultSet = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { - if (resultSet.next()) { - return resultSet.getString(1); + // The writer ID is only returned when connected to a reader, so if the query does not return a value, it + // means we are connected to a writer + try (final ResultSet rs = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { + if (rs.next()) { + return rs.getString(1); } } } + return null; } protected HostSpec createHost( - final ResultSet resultSet, + final ResultSet rs, final HostSpec initialHostSpec, - final HostSpec hostTemplate, + final HostSpec instanceTemplate, final @Nullable String writerId) throws SQLException { - String endpoint = resultSet.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" + String endpoint = rs.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" - String hostId = resultSet.getString("id"); // "1034958454" + String hostId = rs.getString("id"); // "1034958454" final boolean isWriter = hostId.equals(writerId); - return createHost(hostId, instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, hostTemplate); + return createHost( + hostId, instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 12b2c70a1..4bd523e39 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -34,6 +34,7 @@ import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUrlType; @@ -86,7 +87,7 @@ public class RdsHostListProvider implements DynamicHostListProvider { protected List initialHostList = new ArrayList<>(); protected HostSpec initialHostSpec; protected String clusterId; - protected HostSpec clusterInstanceTemplate; + protected HostSpec instanceTemplate; protected volatile boolean isInitialized = false; @@ -124,14 +125,12 @@ protected void init() throws SQLException { } protected void initSettings() throws SQLException { - - // initial topology is based on connection string + // The initial topology is based on the connection string. this.initialHostList = connectionUrlParser.getHostsFromConnectionUrl(this.originalUrl, false, this.hostListProviderService::getHostSpecBuilder); if (this.initialHostList == null || this.initialHostList.isEmpty()) { - throw new SQLException(Messages.get("RdsHostListProvider.parsedListEmpty", - new Object[] {this.originalUrl})); + throw new SQLException(Messages.get("RdsHostListProvider.parsedListEmpty", new Object[] {this.originalUrl})); } this.initialHostSpec = this.initialHostList.get(0); this.hostListProviderService.setInitialConnectionHostSpec(this.initialHostSpec); @@ -143,10 +142,10 @@ protected void initSettings() throws SQLException { HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); String clusterInstancePattern = CLUSTER_INSTANCE_HOST_PATTERN.getString(this.properties); if (clusterInstancePattern != null) { - this.clusterInstanceTemplate = + this.instanceTemplate = ConnectionUrlParser.parseHostPortPair(clusterInstancePattern, () -> hostSpecBuilder); } else { - this.clusterInstanceTemplate = + this.instanceTemplate = hostSpecBuilder .host(rdsHelper.getRdsInstanceHostPattern(this.initialHostSpec.getHost())) .hostId(this.initialHostSpec.getHostId()) @@ -154,8 +153,7 @@ protected void initSettings() throws SQLException { .build(); } - validateHostPatternSetting(this.clusterInstanceTemplate.getHost()); - + validateHostPatternSetting(this.instanceTemplate.getHost()); this.rdsUrlType = rdsHelper.identifyRdsType(this.initialHostSpec.getHost()); } @@ -175,20 +173,15 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f init(); final List storedHosts = this.getStoredTopology(); - if (storedHosts == null || forceUpdate) { - - // need to re-fetch topology - + // We need to re-fetch topology. if (conn == null) { - // can't fetch the latest topology since no connection - // return original hosts parsed from connection string + // We cannot fetch the latest topology since we do not have access to a connection, so we return the original + // hosts parsed from the connection string. return new FetchTopologyResult(false, this.initialHostList); } - // fetch topology from the DB final List hosts = this.queryForTopology(conn); - if (!Utils.isNullOrEmpty(hosts)) { this.servicesContainer.getStorageService().set(this.clusterId, new Topology(hosts)); return new FetchTopologyResult(false, hosts); @@ -198,7 +191,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f if (storedHosts == null) { return new FetchTopologyResult(false, this.initialHostList); } else { - // use cached data + // Return the cached data. return new FetchTopologyResult(true, storedHosts); } } @@ -212,7 +205,7 @@ protected FetchTopologyResult getTopology(final Connection conn, final boolean f */ protected List queryForTopology(final Connection conn) throws SQLException { init(); - return this.topologyUtils.queryForTopology(conn, this.initialHostSpec, this.clusterInstanceTemplate); + return this.topologyUtils.queryForTopology(conn, this.initialHostSpec, this.instanceTemplate); } /** @@ -246,7 +239,7 @@ public List refresh(final Connection connection) throws SQLException { : this.hostListProviderService.getCurrentConnection(); final FetchTopologyResult results = getTopology(currentConnection, false); - LOGGER.finest(() -> Utils.logTopology(results.hosts, results.isCachedData ? "[From cache] Topology:" : null)); + LOGGER.finest(() -> LogUtils.logTopology(results.hosts, results.isCachedData ? "[From cache] Topology:" : null)); this.hostList = results.hosts; return Collections.unmodifiableList(hostList); @@ -265,7 +258,7 @@ public List forceRefresh(final Connection connection) throws SQLExcept : this.hostListProviderService.getCurrentConnection(); final FetchTopologyResult results = getTopology(currentConnection, true); - LOGGER.finest(() -> Utils.logTopology(results.hosts)); + LOGGER.finest(() -> LogUtils.logTopology(results.hosts)); this.hostList = results.hosts; return Collections.unmodifiableList(this.hostList); } @@ -277,9 +270,6 @@ public RdsUrlType getRdsUrlType() throws SQLException { protected void validateHostPatternSetting(final String hostPattern) { if (!rdsHelper.isDnsPatternValid(hostPattern)) { - // "Invalid value for the 'clusterInstanceHostPattern' configuration setting - the host - // pattern must contain a '?' - // character as a placeholder for the DB instance identifiers of the instances in the cluster" final String message = Messages.get("RdsHostListProvider.invalidPattern"); LOGGER.severe(message); throw new RuntimeException(message); @@ -287,18 +277,13 @@ protected void validateHostPatternSetting(final String hostPattern) { final RdsUrlType rdsUrlType = rdsHelper.identifyRdsType(hostPattern); if (rdsUrlType == RdsUrlType.RDS_PROXY || rdsUrlType == RdsUrlType.RDS_PROXY_ENDPOINT) { - // "An RDS Proxy url can't be used as the 'clusterInstanceHostPattern' configuration setting." - final String message = - Messages.get("RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRDSProxy"); + final String message = Messages.get("RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRDSProxy"); LOGGER.severe(message); throw new RuntimeException(message); } if (rdsUrlType == RdsUrlType.RDS_CUSTOM_CLUSTER) { - // "An RDS Custom Cluster endpoint can't be used as the 'clusterInstanceHostPattern' - // configuration setting." - final String message = - Messages.get("RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRdsCustom"); + final String message = Messages.get("RdsHostListProvider.clusterInstanceHostPatternNotSupportedForRdsCustom"); LOGGER.severe(message); throw new RuntimeException(message); } @@ -338,7 +323,6 @@ public HostRole getHostRole(Connection conn) throws SQLException { } if (topology == null) { - // TODO: above, we throw an exception, but here, we return null. Should we stick with just one? return null; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java index 8229e2cd3..13e646a03 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/StaticHostListProvider.java @@ -16,6 +16,6 @@ package software.amazon.jdbc.hostlistprovider; -// A marker interface for providers that fetch node lists, and it never changes since after. -// An example of such provider is a provider that use connection string as a source. +// A marker interface for providers that fetch host lists that do not change over time. +// An example is a provider that uses a connection string to determine the host list. public interface StaticHostListProvider extends HostListProvider {} diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index b926cee6a..b5a838bd1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -54,18 +54,18 @@ public TopologyUtils( this.hostSpecBuilder = hostSpecBuilder; } - public @Nullable List queryForTopology(Connection conn, HostSpec initialHostSpec, HostSpec hostTemplate) + public @Nullable List queryForTopology(Connection conn, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException { int networkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.dialect.getTopologyQuery())) { - if (resultSet.getMetaData().getColumnCount() == 0) { + final ResultSet rs = stmt.executeQuery(this.dialect.getTopologyQuery())) { + if (rs.getMetaData().getColumnCount() == 0) { // We expect at least 4 columns. Note that the server may return 0 columns if failover has occurred. LOGGER.finest(Messages.get("TopologyUtils.unexpectedTopologyQueryColumnCount")); return null; } - return this.verifyWriter(this.getHosts(conn, resultSet, initialHostSpec, hostTemplate)); + return this.verifyWriter(this.getHosts(conn, rs, initialHostSpec, instanceTemplate)); } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { @@ -79,21 +79,24 @@ protected int setNetworkTimeout(Connection conn) { int networkTimeout = -1; try { networkTimeout = conn.getNetworkTimeout(); - // The topology query is not monitored by the EFM plugin, so it needs a socket timeout + // The topology query is not monitored by the EFM plugin, so it needs a socket timeout. if (networkTimeout == 0) { conn.setNetworkTimeout(this.networkTimeoutExecutor, DEFAULT_QUERY_TIMEOUT_MS); } } catch (SQLException e) { - LOGGER.warning(() -> Messages.get("TopologyUtils.errorGettingNetworkTimeout", - new Object[] {e.getMessage()})); + LOGGER.warning(() -> Messages.get("TopologyUtils.errorGettingNetworkTimeout", new Object[] {e.getMessage()})); } return networkTimeout; } protected abstract @Nullable List getHosts( - Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec hostTemplate) throws SQLException; + Connection conn, ResultSet rs, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException; + + protected @Nullable List verifyWriter(@Nullable List allHosts) { + if (allHosts == null) { + return null; + } - protected @Nullable List verifyWriter(List allHosts) { List hosts = new ArrayList<>(); List writers = new ArrayList<>(); for (HostSpec host : allHosts) { @@ -128,11 +131,11 @@ public HostSpec createHost( final long weight, final Timestamp lastUpdateTime, final HostSpec initialHostSpec, - final HostSpec clusterInstanceTemplate) { + final HostSpec instanceTemplate) { instanceName = instanceName == null ? "?" : instanceName; - final String endpoint = clusterInstanceTemplate.getHost().replace("?", instanceName); - final int port = clusterInstanceTemplate.isPortSpecified() - ? clusterInstanceTemplate.getPort() + final String endpoint = instanceTemplate.getHost().replace("?", instanceName); + final int port = instanceTemplate.isPortSpecified() + ? instanceTemplate.getPort() : initialHostSpec.getPort(); final HostSpec hostSpec = this.hostSpecBuilder @@ -163,9 +166,9 @@ public HostSpec createHost( public @Nullable Pair getInstanceId(final Connection connection) { try { try (final Statement stmt = connection.createStatement(); - final ResultSet resultSet = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { - if (resultSet.next()) { - return Pair.create(resultSet.getString(1), resultSet.getString(2)); + final ResultSet rs = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { + if (rs.next()) { + return Pair.create(rs.getString(1), rs.getString(2)); } } } catch (SQLException ex) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 0271b2e1e..1c0f481f6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -42,6 +42,7 @@ import software.amazon.jdbc.hostlistprovider.TopologyUtils; import software.amazon.jdbc.util.ExecutorFactory; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.PropertyUtils; @@ -63,7 +64,7 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected static final int defaultConnectionTimeoutMs = 5000; protected static final int defaultSocketTimeoutMs = 5000; - // Keep monitoring topology with a high rate for 30s after failover. + // Keep monitoring topology at a high rate for 30s after failover. protected static final long highRefreshPeriodAfterPanicNano = TimeUnit.SECONDS.toNanos(30); protected static final long ignoreTopologyRequestNano = TimeUnit.SECONDS.toNanos(10); @@ -89,7 +90,7 @@ public class ClusterTopologyMonitorImpl extends AbstractMonitor implements Clust protected final Properties properties; protected final Properties monitoringProperties; protected final HostSpec initialHostSpec; - protected final HostSpec clusterInstanceTemplate; + protected final HostSpec instanceTemplate; protected ExecutorService nodeExecutorService = null; protected boolean isVerifiedWriterConnection = false; @@ -102,7 +103,7 @@ public ClusterTopologyMonitorImpl( final String clusterId, final HostSpec initialHostSpec, final Properties properties, - final HostSpec clusterInstanceTemplate, + final HostSpec instanceTemplate, final long refreshRateNano, final long highRefreshRateNano) { super(monitorTerminationTimeoutSec); @@ -111,7 +112,7 @@ public ClusterTopologyMonitorImpl( this.topologyUtils = topologyUtils; this.clusterId = clusterId; this.initialHostSpec = initialHostSpec; - this.clusterInstanceTemplate = clusterInstanceTemplate; + this.instanceTemplate = instanceTemplate; this.properties = properties; this.refreshRateNano = refreshRateNano; this.highRefreshRateNano = highRefreshRateNano; @@ -150,10 +151,11 @@ public List forceRefresh(final boolean shouldVerifyWriter, final long if (this.ignoreNewTopologyRequestsEndTimeNano.get() > 0 && System.nanoTime() < this.ignoreNewTopologyRequestsEndTimeNano.get()) { - // Previous failover has just completed. We can use results of it without triggering a new topology update. + // A previous failover event has completed recently. + // We can use the results of it without triggering a new topology update. List currentHosts = getStoredHosts(); LOGGER.finest( - Utils.logTopology(currentHosts, Messages.get("ClusterTopologyMonitorImpl.ignoringTopologyRequest"))); + LogUtils.logTopology(currentHosts, Messages.get("ClusterTopologyMonitorImpl.ignoringTopologyRequest"))); if (currentHosts != null) { return currentHosts; } @@ -173,11 +175,11 @@ public List forceRefresh(final boolean shouldVerifyWriter, final long public List forceRefresh(@Nullable Connection connection, final long timeoutMs) throws SQLException, TimeoutException { if (this.isVerifiedWriterConnection) { - // Push monitoring thread to refresh topology with a verified connection + // Get the monitoring thread to refresh the topology using a verified connection. return this.waitTillTopologyGetsUpdated(timeoutMs); } - // Otherwise use provided unverified connection to update topology + // Otherwise, use the provided unverified connection to update the topology. return this.fetchTopologyAndUpdateCache(connection); } @@ -188,12 +190,12 @@ protected List waitTillTopologyGetsUpdated(final long timeoutMs) throw synchronized (this.requestToUpdateTopology) { this.requestToUpdateTopology.set(true); - // Notify monitoring thread (that might be sleeping) that topology should be refreshed immediately. + // Notify the monitoring thread, which may be sleeping, that topology should be refreshed immediately. this.requestToUpdateTopology.notifyAll(); } if (timeoutMs == 0) { - LOGGER.finest(Utils.logTopology(currentHosts, Messages.get("ClusterTopologyMonitorImpl.timeoutSetToZero"))); + LOGGER.finest(LogUtils.logTopology(currentHosts, Messages.get("ClusterTopologyMonitorImpl.timeoutSetToZero"))); return currentHosts; } @@ -233,7 +235,7 @@ public void stop() { this.nodeThreadsStop.set(true); this.shutdownNodeExecutorService(); - // It breaks a waiting/sleeping cycles in monitoring thread + // This code interrupts the waiting/sleeping cycle in the monitoring thread. synchronized (this.requestToUpdateTopology) { this.requestToUpdateTopology.set(true); this.requestToUpdateTopology.notifyAll(); @@ -264,7 +266,7 @@ public void monitor() throws Exception { if (this.submittedNodes.isEmpty()) { LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.startingNodeMonitoringThreads")); - // start node threads + // Start node monitors. this.nodeThreadsStop.set(false); this.nodeThreadsWriterConnection.set(null); this.nodeThreadsReaderConnection.set(null); @@ -273,7 +275,7 @@ public void monitor() throws Exception { List hosts = getStoredHosts(); if (hosts == null) { - // need any connection to get topology + // Use any available connection to get the topology. hosts = this.openAnyConnectionAndUpdateTopology(); } @@ -304,20 +306,17 @@ public void monitor() throws Exception { throw exceptionList.get(0); } } - // It's not possible to call shutdown() on this.nodeExecutorService since more node may be added later. + // We do not call nodeExecutorService.shutdown() here since more node monitors may be submitted later. } - // otherwise let's try it again the next round - + // We will try again in the next iteration. } else { - // node threads are running - // check if writer is already detected + // The node monitors are running, so we check if the writer has been detected. final Connection writerConnection = this.nodeThreadsWriterConnection.get(); final HostSpec writerConnectionHostSpec = this.nodeThreadsWriterHostSpec.get(); if (writerConnection != null && writerConnectionHostSpec != null) { - LOGGER.finest( - Messages.get( - "ClusterTopologyMonitorImpl.writerPickedUpFromNodeMonitors", - new Object[] {writerConnectionHostSpec})); + LOGGER.finest(Messages.get( + "ClusterTopologyMonitorImpl.writerPickedUpFromNodeMonitors", + new Object[] {writerConnectionHostSpec})); this.closeConnection(this.monitoringConnection.get()); this.monitoringConnection.set(writerConnection); @@ -339,7 +338,7 @@ public void monitor() throws Exception { continue; } else { - // update node threads with new nodes in the topology + // Update node monitors with the new instances in the topology List hosts = this.nodeThreadsLatestTopology.get(); if (hosts != null && !this.nodeThreadsStop.get()) { for (HostSpec hostSpec : hosts) { @@ -363,7 +362,7 @@ public void monitor() throws Exception { throw exceptionList.get(0); } } - // It's not possible to call shutdown() on this.nodeExecutorService since more node may be added later. + // We do not call nodeExecutorService.shutdown() here since more node monitors may be submitted later. } } } @@ -371,8 +370,7 @@ public void monitor() throws Exception { this.delay(true); } else { - // regular mode (not panic mode) - + // We are in regular mode (not panic mode). if (!this.submittedNodes.isEmpty()) { this.shutdownNodeExecutorService(); this.submittedNodes.clear(); @@ -380,8 +378,7 @@ public void monitor() throws Exception { final List hosts = this.fetchTopologyAndUpdateCache(this.monitoringConnection.get()); if (hosts == null) { - // can't get topology - // let's switch to panic mode + // Attempt to fetch topology failed, so we switch to panic mode. Connection conn = this.monitoringConnection.get(); this.monitoringConnection.set(null); this.isVerifiedWriterConnection = false; @@ -393,9 +390,9 @@ public void monitor() throws Exception { this.highRefreshRateEndTimeNano = 0; } - // Do not log topology while in high refresh rate. It's noisy! + // We avoid logging the topology while using the high refresh rate because it is too noisy. if (this.highRefreshRateEndTimeNano == 0) { - LOGGER.finest(Utils.logTopology(getStoredHosts())); + LOGGER.finest(LogUtils.logTopology(getStoredHosts())); } this.delay(false); @@ -410,9 +407,9 @@ public void monitor() throws Exception { } catch (final InterruptedException intEx) { Thread.currentThread().interrupt(); } catch (final Exception ex) { - // this should not be reached; log and exit thread + // This should not be reached. if (LOGGER.isLoggable(Level.FINEST)) { - // We want to print full trace stack of the exception. + // We want to print the full trace stack of the exception. LOGGER.log( Level.FINEST, Messages.get( @@ -455,7 +452,7 @@ protected void shutdownNodeExecutorService() { this.nodeExecutorService.shutdownNow(); } } catch (InterruptedException e) { - // do nothing + // Do nothing. } this.nodeExecutorService = null; @@ -492,11 +489,10 @@ protected List openAnyConnectionAndUpdateTopology() { Connection conn; - // open a new connection + // Open a new connection. try { conn = this.servicesContainer.getPluginService().forceConnect(this.initialHostSpec, this.monitoringProperties); } catch (SQLException ex) { - // can't connect return null; } @@ -512,34 +508,29 @@ protected List openAnyConnectionAndUpdateTopology() { if (rdsHelper.isRdsInstance(this.initialHostSpec.getHost())) { this.writerHostSpec.set(this.initialHostSpec); - LOGGER.finest( - Messages.get( - "ClusterTopologyMonitorImpl.writerMonitoringConnection", - new Object[] {this.writerHostSpec.get().getHost()})); + LOGGER.finest(Messages.get( + "ClusterTopologyMonitorImpl.writerMonitoringConnection", + new Object[] {this.writerHostSpec.get().getHost()})); } else { final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); - // TODO: this code isn't quite right when compared to main-3.x if (pair != null) { - HostSpec hostTemplate = - this.getClusterInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); - HostSpec writerHost = - this.topologyUtils.createHost(pair.getValue1(), pair.getValue2(), true, 0, null, - this.initialHostSpec, hostTemplate); + HostSpec instanceTemplate = this.getinstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); + HostSpec writerHost = this.topologyUtils.createHost( + pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, instanceTemplate); this.writerHostSpec.set(writerHost); - LOGGER.finest( - Messages.get( - "ClusterTopologyMonitorImpl.writerMonitoringConnection", - new Object[] {this.writerHostSpec.get().getHost()})); + LOGGER.finest(Messages.get( + "ClusterTopologyMonitorImpl.writerMonitoringConnection", + new Object[] {this.writerHostSpec.get().getHost()})); } } } } catch (SQLException ex) { - // do nothing + // Do nothing. } } else { - // monitoring connection has already been set by other thread - // close new connection as we don't need it + // The monitoring connection has already been detected by another thread. We close the new connection since it + // is not needed anymore. this.closeConnection(conn); } } @@ -555,8 +546,7 @@ protected List openAnyConnectionAndUpdateTopology() { } if (hosts == null) { - // can't get topology; it might be something's wrong with a connection - // close connection + // Attempt to fetch topology failed. There might be something wrong with the connection, so we close it here. Connection connToClose = this.monitoringConnection.get(); this.monitoringConnection.set(null); this.closeConnection(connToClose); @@ -566,8 +556,8 @@ protected List openAnyConnectionAndUpdateTopology() { return hosts; } - protected HostSpec getClusterInstanceTemplate(String nodeId, Connection connection) throws SQLException { - return this.clusterInstanceTemplate; + protected HostSpec getinstanceTemplate(String nodeId, Connection connection) throws SQLException { + return this.instanceTemplate; } protected void closeConnection(final @Nullable Connection connection) { @@ -576,16 +566,16 @@ protected void closeConnection(final @Nullable Connection connection) { try { connection.setNetworkTimeout(networkTimeoutExecutor, closeConnectionNetworkTimeoutMs); } catch (SQLException ex) { - // do nothing + // Do nothing. } connection.close(); } } catch (final SQLException ex) { - // ignore + // Do nothing. } } - // Sleep that can be easily interrupted + // Sleep method that can be easily interrupted. protected void delay(boolean useHighRefreshRate) throws InterruptedException { if (this.highRefreshRateEndTimeNano > 0 && System.nanoTime() < this.highRefreshRateEndTimeNano) { useHighRefreshRate = true; @@ -617,11 +607,12 @@ protected void delay(boolean useHighRefreshRate) throws InterruptedException { } catch (SQLException ex) { LOGGER.finest(Messages.get("ClusterTopologyMonitorImpl.errorFetchingTopology", new Object[] {ex})); } + return null; } protected List queryForTopology(Connection connection) throws SQLException { - return this.topologyUtils.queryForTopology(connection, this.initialHostSpec, this.clusterInstanceTemplate); + return this.topologyUtils.queryForTopology(connection, this.initialHostSpec, this.instanceTemplate); } protected void updateTopologyCache(final @NonNull List hosts) { @@ -685,7 +676,8 @@ public void run() { try { isWriter = this.monitor.topologyUtils.isWriterInstance(connection); } catch (SQLSyntaxErrorException ex) { - LOGGER.severe(() -> Messages.get("NodeMonitoringThread.invalidWriterQuery", + LOGGER.severe(() -> Messages.get( + "NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); throw new RuntimeException(ex); @@ -707,13 +699,12 @@ public void run() { } if (isWriter) { - // this prevents closing connection in finally block + // This prevents us from closing the connection in the finally block. if (!this.monitor.nodeThreadsWriterConnection.compareAndSet(null, connection)) { - // writer connection is already setup + // The writer connection is already set up, probably by another node monitor. this.monitor.closeConnection(connection); - } else { - // writer connection is successfully set to writerConnection + // Successfully updated the node monitor writer connection. LOGGER.fine(Messages.get("NodeMonitoringThread.detectedWriter", new Object[] {hostSpec.getUrl()})); // When nodeThreadsWriterConnection and nodeThreadsWriterHostSpec are both set, the topology monitor may // set ignoreNewTopologyRequestsEndTimeNano, in which case other threads will use the cached topology @@ -721,23 +712,22 @@ public void run() { this.monitor.fetchTopologyAndUpdateCache(connection); this.monitor.nodeThreadsWriterHostSpec.set(hostSpec); this.monitor.nodeThreadsStop.set(true); - LOGGER.fine(Utils.logTopology(this.monitor.getStoredHosts())); + LOGGER.fine(LogUtils.logTopology(this.monitor.getStoredHosts())); } - // Setting the connection to null here prevents the final block - // from closing nodeThreadsWriterConnection. + // We set the connection to null to prevent the finally block from closing nodeThreadsWriterConnection. connection = null; return; - } else if (connection != null) { - // this connection is a reader connection + // This connection is a reader connection. if (this.monitor.nodeThreadsWriterConnection.get() == null) { - // while writer connection isn't yet established this reader connection may update topology + // We can use this reader connection to update the topology while we wait for the writer connection to + // be established. if (updateTopology) { this.readerThreadFetchTopology(connection, this.writerHostSpec); } else if (this.monitor.nodeThreadsReaderConnection.get() == null) { if (this.monitor.nodeThreadsReaderConnection.compareAndSet(null, connection)) { - // let's use this connection to update topology + // Use this connection to update the topology. updateTopology = true; this.readerThreadFetchTopology(connection, this.writerHostSpec); } @@ -752,7 +742,8 @@ public void run() { } finally { this.monitor.closeConnection(connection); final long end = System.nanoTime(); - LOGGER.finest(() -> Messages.get("NodeMonitoringThread.threadCompleted", + LOGGER.finest(() -> Messages.get( + "NodeMonitoringThread.threadCompleted", new Object[] {TimeUnit.NANOSECONDS.toMillis(end - start)})); } } @@ -765,7 +756,7 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla List hosts; try { hosts = this.monitor.topologyUtils.queryForTopology( - connection, this.monitor.initialHostSpec, this.monitor.clusterInstanceTemplate); + connection, this.monitor.initialHostSpec, this.monitor.instanceTemplate); if (hosts == null) { return; } @@ -773,12 +764,12 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla return; } - // share this topology so the main monitoring thread be able to adjust node monitoring threads + // Share this topology so that the main monitoring thread can adjust the node monitoring threads. this.monitor.nodeThreadsLatestTopology.set(hosts); if (this.writerChanged) { this.monitor.updateTopologyCache(hosts); - LOGGER.finest(Utils.logTopology(hosts)); + LOGGER.finest(LogUtils.logTopology(hosts)); return; } @@ -789,16 +780,14 @@ private void readerThreadFetchTopology(final Connection connection, final @Nulla if (latestWriterHostSpec != null && writerHostSpec != null && !latestWriterHostSpec.getHostAndPort().equals(writerHostSpec.getHostAndPort())) { - - // writer node has changed this.writerChanged = true; - - LOGGER.fine(() -> Messages.get("NodeMonitoringThread.writerNodeChanged", + LOGGER.fine(() -> Messages.get( + "NodeMonitoringThread.writerNodeChanged", new Object[] {writerHostSpec.getHost(), latestWriterHostSpec.getHost()})); - // we can update topology cache and notify all waiting threads + // Update the topology cache and notify all waiting threads. this.monitor.updateTopologyCache(hosts); - LOGGER.fine(Utils.logTopology(hosts)); + LOGGER.fine(LogUtils.logTopology(hosts)); } } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java index 9c135312e..cf5e20a7d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java @@ -21,7 +21,6 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.logging.Logger; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; import software.amazon.jdbc.util.FullServicesContainer; @@ -39,7 +38,7 @@ public GlobalAuroraTopologyMonitor( final String clusterId, final HostSpec initialHostSpec, final Properties properties, - final HostSpec clusterInstanceTemplate, + final HostSpec instanceTemplate, final long refreshRateNano, final long highRefreshRateNano, final Map instanceTemplatesByRegion) { @@ -48,7 +47,7 @@ public GlobalAuroraTopologyMonitor( clusterId, initialHostSpec, properties, - clusterInstanceTemplate, + instanceTemplate, refreshRateNano, highRefreshRateNano); @@ -57,19 +56,19 @@ public GlobalAuroraTopologyMonitor( } @Override - protected HostSpec getClusterInstanceTemplate(String instanceId, Connection connection) throws SQLException { + protected HostSpec getinstanceTemplate(String instanceId, Connection connection) throws SQLException { String region = this.topologyUtils.getRegion(instanceId, connection); if (!StringUtils.isNullOrEmpty(region)) { - final HostSpec clusterInstanceTemplateForRegion = this.instanceTemplatesByRegion.get(region); - if (clusterInstanceTemplateForRegion == null) { + final HostSpec instanceTemplate = this.instanceTemplatesByRegion.get(region); + if (instanceTemplate == null) { throw new SQLException( Messages.get("GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {region})); } - return clusterInstanceTemplateForRegion; + return instanceTemplate; } - return this.clusterInstanceTemplate; + return this.instanceTemplate; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java index b42cf80f0..b258c223d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringGlobalAuroraHostListProvider.java @@ -18,22 +18,18 @@ import java.sql.Connection; import java.sql.SQLException; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; -import java.util.stream.Collectors; import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.HostSpecBuilder; import software.amazon.jdbc.PropertyDefinition; import software.amazon.jdbc.hostlistprovider.GlobalAuroraHostListProvider; import software.amazon.jdbc.hostlistprovider.GlobalAuroraTopologyUtils; -import software.amazon.jdbc.util.ConnectionUrlParser; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.StringUtils; @@ -47,7 +43,7 @@ public class MonitoringGlobalAuroraHostListProvider extends MonitoringRdsHostLis protected final GlobalAuroraTopologyUtils topologyUtils; static { - // Intentionally register property definition in GlobalAuroraHostListProvider class. + // Intentionally register property definition using the GlobalAuroraHostListProvider class. PropertyDefinition.registerPluginProperties(GlobalAuroraHostListProvider.class); } @@ -64,28 +60,9 @@ public MonitoringGlobalAuroraHostListProvider( protected void initSettings() throws SQLException { super.initSettings(); - // TODO: check if we have other places that parse into string-HostSpec maps, consider refactoring - String templates = GlobalAuroraHostListProvider.GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); - if (StringUtils.isNullOrEmpty(templates)) { - throw new SQLException(Messages.get("MonitoringGlobalAuroraHostListProvider.globalHostPatternsRequired")); - } - - HostSpecBuilder hostSpecBuilder = this.hostListProviderService.getHostSpecBuilder(); - this.instanceTemplatesByRegion = Arrays.stream(templates.split(",")) - .map(x -> ConnectionUrlParser.parseHostPortPairWithRegionPrefix(x.trim(), () -> hostSpecBuilder)) - .collect(Collectors.toMap( - Pair::getValue1, - v -> { - this.validateHostPatternSetting(v.getValue2().getHost()); - return v.getValue2(); - })); - LOGGER.finest(Messages.get( - "GlobalAuroraHostListProvider.detectedGdbPatterns", new Object[] { - this.instanceTemplatesByRegion.entrySet().stream() - .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) - .collect(Collectors.joining("\n")) - }) - ); + String instanceTemplates = GlobalAuroraHostListProvider.GLOBAL_CLUSTER_INSTANCE_HOST_PATTERNS.getString(properties); + this.instanceTemplatesByRegion = + this.topologyUtils.parseInstanceTemplates(instanceTemplates, this::validateHostPatternSetting); } protected ClusterTopologyMonitor initMonitor() throws SQLException { @@ -101,7 +78,7 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.clusterId, this.initialHostSpec, this.properties, - this.clusterInstanceTemplate, + this.instanceTemplate, this.refreshRateNano, this.highRefreshRateNano, this.instanceTemplatesByRegion)); diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java index 4f1a82080..a1ab3490c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/MonitoringRdsHostListProvider.java @@ -73,7 +73,7 @@ protected ClusterTopologyMonitor initMonitor() throws SQLException { this.clusterId, this.initialHostSpec, this.properties, - this.clusterInstanceTemplate, + this.instanceTemplate, this.refreshRateNano, this.highRefreshRateNano)); } @@ -108,6 +108,6 @@ public List forceRefresh(final boolean shouldVerifyWriter, final long @Override public void releaseResources() { - // do nothing + // Do nothing. } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java index 63a2cef2a..622bc2eb0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java @@ -74,8 +74,8 @@ public String toString() { .map(x -> String.format("%s -> %s", x.getKey(), x.getValue())) .collect(Collectors.joining("\n ")); String allHostNamesStr = String.join("\n ", this.hostNames); - String startTopologyStr = Utils.logTopology(this.startTopology); - String currentTopologyStr = Utils.logTopology(this.currentTopology); + String startTopologyStr = LogUtils.logTopology(this.startTopology); + String currentTopologyStr = LogUtils.logTopology(this.currentTopology); return String.format("%s [\n" + " phase %s, \n" + " version '%s', \n" diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 8504842c6..1412d47e1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -465,7 +465,7 @@ private boolean refreshTopologyAndConnectToNewWriter() throws InterruptedExcepti if (allowOldWriter || !isSame(writerCandidate, this.originalWriterHost)) { // new writer is available, and it's different from the previous writer - LOGGER.finest(() -> Utils.logTopology(this.currentTopology, "[TaskB] Topology:")); + LOGGER.finest(() -> LogUtils.logTopology(this.currentTopology, "[TaskB] Topology:")); if (connectToWriter(writerCandidate)) { return true; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 731e46b90..3c830d75d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -757,7 +757,7 @@ protected void failoverWriter() throws SQLException { throwFailoverFailedException( Messages.get( "Failover.noWriterHostAfterReconnecting", - new Object[]{Utils.logTopology(hosts, "")})); + new Object[]{LogUtils.logTopology(hosts, "")})); return; } @@ -765,7 +765,7 @@ protected void failoverWriter() throws SQLException { if (!Utils.containsHostAndPort(allowedHosts, writerHostSpec.getHostAndPort())) { throwFailoverFailedException( Messages.get("Failover.newWriterNotAllowed", - new Object[] {writerHostSpec.getUrl(), Utils.logTopology(allowedHosts, "")})); + new Object[] {writerHostSpec.getUrl(), LogUtils.logTopology(allowedHosts, "")})); return; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 1ca39eb6a..9e78c82e9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -430,7 +430,7 @@ protected ReaderFailoverResult getReaderFailoverConnection(long failoverEndTimeN this.failoverReaderHostSelectorStrategySetting); } catch (UnsupportedOperationException | SQLException ex) { LOGGER.finest( - Utils.logTopology( + LogUtils.logTopology( new ArrayList<>(remainingReaders), Messages.get("Failover.errorSelectingReaderHost", new Object[]{ex.getMessage()}))); break; @@ -438,7 +438,7 @@ protected ReaderFailoverResult getReaderFailoverConnection(long failoverEndTimeN if (readerCandidate == null) { LOGGER.finest( - Utils.logTopology(new ArrayList<>(remainingReaders), Messages.get("Failover.readerCandidateNull"))); + LogUtils.logTopology(new ArrayList<>(remainingReaders), Messages.get("Failover.readerCandidateNull"))); break; } @@ -559,7 +559,7 @@ protected void failoverWriter() throws SQLException { if (this.failoverWriterFailedCounter != null) { this.failoverWriterFailedCounter.inc(); } - String message = Utils.logTopology(updatedHosts, Messages.get("Failover.noWriterHost")); + String message = LogUtils.logTopology(updatedHosts, Messages.get("Failover.noWriterHost")); LOGGER.severe(message); throw new FailoverFailedSQLException(message); } @@ -569,7 +569,7 @@ protected void failoverWriter() throws SQLException { if (this.failoverWriterFailedCounter != null) { this.failoverWriterFailedCounter.inc(); } - String topologyString = Utils.logTopology(allowedHosts, ""); + String topologyString = LogUtils.logTopology(allowedHosts, ""); LOGGER.severe(Messages.get("Failover.newWriterNotAllowed", new Object[] {writerCandidate.getUrl(), topologyString})); throw new FailoverFailedSQLException( diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java index f4075a285..ec350aab6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java @@ -116,7 +116,7 @@ public void monitor() { List newLimitlessRouters = queryHelper.queryForLimitlessRouters(this.monitoringConn, this.hostSpec.getPort()); this.storageService.set(this.limitlessRouterCacheKey, new LimitlessRouters(newLimitlessRouters)); - LOGGER.finest(Utils.logTopology(newLimitlessRouters, "[limitlessRouterMonitor] Topology:")); + LOGGER.finest(LogUtils.logTopology(newLimitlessRouters, "[limitlessRouterMonitor] Topology:")); TimeUnit.MILLISECONDS.sleep(this.intervalMs); // do not include this in the telemetry } catch (final Exception ex) { if (telemetryContext != null) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index d03d6530d..b44a2c927 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -425,7 +425,7 @@ private void switchToReaderConnection(final List hosts) LOGGER.finest( Messages.get( "ReadWriteSplittingPlugin.previousReaderNotAllowed", - new Object[] {this.readerHostSpec, Utils.logTopology(hosts, "")})); + new Object[] {this.readerHostSpec, LogUtils.logTopology(hosts, "")})); closeConnectionIfIdle(this.readerConnection); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index c1d095581..f1d7b9114 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -101,7 +101,7 @@ public Connection getVerifiedConnection( this.pluginService.refreshHostList(conn); } - LOGGER.finest(() -> Utils.logTopology(this.pluginService.getAllHosts())); + LOGGER.finest(() -> LogUtils.logTopology(this.pluginService.getAllHosts())); if (this.writerHostSpec == null) { final HostSpec writerCandidate = Utils.getWriter(this.pluginService.getAllHosts()); @@ -149,7 +149,7 @@ public Connection getVerifiedConnection( Messages.get("AuroraStaleDnsHelper.currentWriterNotAllowed", new Object[] { this.writerHostSpec == null ? "" : this.writerHostSpec.getHostAndPort(), - Utils.logTopology(allowedHosts, "")}) + LogUtils.logTopology(allowedHosts, "")}) ); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/LogUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/LogUtils.java new file mode 100644 index 000000000..932e4a21e --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/util/LogUtils.java @@ -0,0 +1,55 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.util; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.checkerframework.checker.nullness.qual.Nullable; +import software.amazon.jdbc.HostSpec; + +public class LogUtils { + public static String logTopology(final @Nullable List hosts) { + return logTopology(hosts, null); + } + + public static String logTopology( + final @Nullable List hosts, + final @Nullable String messagePrefix) { + + final StringBuilder msg = new StringBuilder(); + if (hosts == null) { + msg.append(""); + } else { + for (final HostSpec host : hosts) { + if (msg.length() > 0) { + msg.append("\n"); + } + msg.append(" ").append(host == null ? "" : host); + } + } + + return Messages.get("Utils.topology", + new Object[] {messagePrefix == null ? "Topology:" : messagePrefix, msg.toString()}); + } + + public static String toLogString(Map map) { + return map.entrySet().stream() + .map(x -> String.format("\t[%s] -> %s", x.getKey(), x.getValue().getHostAndPort())) + .collect(Collectors.joining("\n")); + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/Utils.java b/wrapper/src/main/java/software/amazon/jdbc/util/Utils.java index 4d02e9224..8bfe5ca1b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/Utils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/Utils.java @@ -53,28 +53,4 @@ public static boolean containsHostAndPort(final Collection hosts, Stri } return null; } - - public static String logTopology(final @Nullable List hosts) { - return logTopology(hosts, null); - } - - public static String logTopology( - final @Nullable List hosts, - final @Nullable String messagePrefix) { - - final StringBuilder msg = new StringBuilder(); - if (hosts == null) { - msg.append(""); - } else { - for (final HostSpec host : hosts) { - if (msg.length() > 0) { - msg.append("\n"); - } - msg.append(" ").append(host == null ? "" : host); - } - } - - return Messages.get("Utils.topology", - new Object[] {messagePrefix == null ? "Topology:" : messagePrefix, msg.toString()}); - } } diff --git a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties index 5a0e509fe..5b6c7971e 100644 --- a/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties +++ b/wrapper/src/main/resources/aws_advanced_jdbc_wrapper_messages.properties @@ -185,11 +185,11 @@ Failover.skipFailoverOnInterruptedThread=Do not start failover since the current FederatedAuthPlugin.unableToDetermineRegion=Unable to determine connection region. If you are using a non-standard RDS URL, please set the ''{0}'' property. -GlobalAuroraHostListProvider.globalClusterInstanceHostPatternsRequired=Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database. -GlobalAuroraHostListProvider.detectedGdbPatterns=Detected GDB instance template patterns:\n{0} - GlobalAuroraTopologyMonitor.cannotFindRegionTemplate=Cannot find cluster template for region {0}. +GlobalAuroraTopologyUtils.globalClusterInstanceHostPatternsRequired=Parameter 'globalClusterInstanceHostPatterns' is required for Aurora Global Database. +GlobalAuroraTopologyUtils.detectedGdbPatterns=Detected GDB instance template patterns:\n{0} + HostAvailabilityStrategy.invalidMaxRetries=Invalid value of {0} for configuration parameter `hostAvailabilityStrategyMaxRetries`. It must be an integer greater than 1. HostAvailabilityStrategy.invalidInitialBackoffTime=Invalid value of {0} for configuration parameter `hostAvailabilityStrategyInitialBackoffTime`. It must be an integer greater than 1. From 29057d8a86afbc4884407b667f48d745d5925f5c Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 15:49:28 -0800 Subject: [PATCH 30/46] Add DialectUtils#checkExistenceQueries --- .../jdbc/dialect/AuroraMysqlDialect.java | 23 +---------- .../amazon/jdbc/dialect/AuroraPgDialect.java | 35 +++++------------ .../amazon/jdbc/dialect/DialectUtils.java | 38 +++++++++++++++++++ .../dialect/GlobalAuroraMysqlDialect.java | 31 +++++---------- .../jdbc/dialect/GlobalAuroraPgDialect.java | 21 +++------- .../dialect/MultiAzClusterMysqlDialect.java | 31 +++++---------- .../amazon/jdbc/dialect/MysqlDialect.java | 2 + .../amazon/jdbc/dialect/PgDialect.java | 13 ++----- .../amazon/jdbc/dialect/RdsMysqlDialect.java | 12 +----- .../amazon/jdbc/dialect/RdsPgDialect.java | 9 +---- 10 files changed, 83 insertions(+), 132 deletions(-) create mode 100644 wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index deb6c7af8..7197d915a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -17,9 +17,6 @@ package software.amazon.jdbc.dialect; import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; import java.util.Collections; import java.util.List; import software.amazon.jdbc.PluginService; @@ -52,16 +49,7 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, @Override public boolean isDialect(final Connection connection) { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(AURORA_VERSION_EXISTS_QUERY)) { - if (rs.next()) { - return true; - } - } catch (final SQLException ex) { - return false; - } - - return false; + return dialectUtils.checkExistenceQueries(connection, AURORA_VERSION_EXISTS_QUERY); } @Override @@ -103,14 +91,7 @@ public String getIsReaderQuery() { @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { - try { - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(BG_TOPOLOGY_EXISTS_QUERY)) { - return rs.next(); - } - } catch (SQLException ex) { - return false; - } + return dialectUtils.checkExistenceQueries(connection, BG_TOPOLOGY_EXISTS_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index 41ff5d028..db71e9b62 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -76,12 +76,14 @@ public boolean isDialect(final Connection connection) { boolean hasExtensions = false; try (Statement stmt = connection.createStatement(); ResultSet rs = stmt.executeQuery(AURORA_UTILS_EXIST_QUERY)) { - if (rs.next()) { - final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); - LOGGER.finest(Messages.get("AuroraPgDialect.auroraUtils", new Object[] {auroraUtils})); - if (auroraUtils) { - hasExtensions = true; - } + if (!rs.next()) { + return false; + } + + final boolean auroraUtils = rs.getBoolean("aurora_stat_utils"); + LOGGER.finest(Messages.get("AuroraPgDialect.auroraUtils", new Object[] {auroraUtils})); + if (auroraUtils) { + hasExtensions = true; } } catch (SQLException ex) { return false; @@ -91,17 +93,7 @@ public boolean isDialect(final Connection connection) { return false; } - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(TOPOLOGY_EXISTS_QUERY)) { - if (rs.next()) { - LOGGER.finest(() -> "hasTopology: true"); - return true; - } - } catch (final SQLException ex) { - return false; - } - - return false; + return dialectUtils.checkExistenceQueries(connection, TOPOLOGY_EXISTS_QUERY); } @Override @@ -150,14 +142,7 @@ public String getLimitlessRouterEndpointQuery() { @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { - try { - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(BG_TOPOLOGY_EXISTS_QUERY)) { - return rs.next(); - } - } catch (SQLException ex) { - return false; - } + return dialectUtils.checkExistenceQueries(connection, BG_TOPOLOGY_EXISTS_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java new file mode 100644 index 000000000..79f3473b3 --- /dev/null +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright 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. + * 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 software.amazon.jdbc.dialect; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +public class DialectUtils { + public boolean checkExistenceQueries(Connection conn, String... existenceQueries) { + for (String existenceQuery : existenceQueries) { + try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(existenceQuery)) { + if (!rs.next()) { + return false; + } + } catch (SQLException e) { + return false; + } + } + + return true; + } +} diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java index 44db2c5af..334757af9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraMysqlDialect.java @@ -49,33 +49,22 @@ public class GlobalAuroraMysqlDialect extends AuroraMysqlDialect implements Glob @Override public boolean isDialect(final Connection connection) { - try { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(GLOBAL_STATUS_TABLE_EXISTS_QUERY)) { - if (!rs.next()) { - return false; - } - } + if (!dialectUtils.checkExistenceQueries( + connection, GLOBAL_STATUS_TABLE_EXISTS_QUERY, GLOBAL_INSTANCE_STATUS_EXISTS_QUERY)) { + return false; + } - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(GLOBAL_INSTANCE_STATUS_EXISTS_QUERY)) { - if (!rs.next()) { - return false; - } + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { + if (!rs.next()) { + return false; } - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { - if (rs.next()) { - int awsRegionCount = rs.getInt(1); - return awsRegionCount > 1; - } - } + int awsRegionCount = rs.getInt(1); + return awsRegionCount > 1; } catch (final SQLException ex) { return false; } - - return false; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java index 3384461eb..7c060c800 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/GlobalAuroraPgDialect.java @@ -63,32 +63,23 @@ public boolean isDialect(final Connection connection) { } } - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(GLOBAL_STATUS_FUNC_EXISTS_QUERY)) { - if (!rs.next()) { - return false; - } + if (!dialectUtils.checkExistenceQueries( + connection, GLOBAL_STATUS_FUNC_EXISTS_QUERY, GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY)) { + return false; } try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(GLOBAL_INSTANCE_STATUS_FUNC_EXISTS_QUERY)) { + ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { if (!rs.next()) { return false; } - } - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(REGION_COUNT_QUERY)) { - if (rs.next()) { - int awsRegionCount = rs.getInt(1); - return awsRegionCount > 1; - } + int awsRegionCount = rs.getInt(1); + return awsRegionCount > 1; } } catch (final SQLException ex) { return false; } - - return false; } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 6087a6ec7..7ac08fc61 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -61,31 +61,18 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzC @Override public boolean isDialect(final Connection connection) { - try { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { - if (!rs.next()) { - return false; - } - } - - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(TOPOLOGY_QUERY)) { - if (!rs.next()) { - return false; - } - } - - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(REPORT_HOST_EXISTS_QUERY)) { - if (!rs.next()) { - return false; - } + if (!dialectUtils.checkExistenceQueries(connection, TOPOLOGY_TABLE_EXISTS_QUERY, TOPOLOGY_QUERY)) { + return false; + } - final String reportHost = rs.getString(2); // Expected value is an IP address - return !StringUtils.isNullOrEmpty(reportHost); + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery(REPORT_HOST_EXISTS_QUERY)) { + if (!rs.next()) { + return false; } + final String reportHost = rs.getString(2); // Expected value is an IP address + return !StringUtils.isNullOrEmpty(reportHost); } catch (final SQLException ex) { return false; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java index 6d218f703..f24ad1de8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java @@ -36,6 +36,8 @@ public class MysqlDialect implements Dialect { protected static final String VERSION_QUERY = "SHOW VARIABLES LIKE 'version_comment'"; protected static final String HOST_ALIAS_QUERY = "SELECT CONCAT(@@hostname, ':', @@port)"; + protected static final DialectUtils dialectUtils = new DialectUtils(); + private static MySQLExceptionHandler mySQLExceptionHandler; private static final EnumSet NO_FAILOVER_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java index 855e968e7..13031f012 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java @@ -41,6 +41,8 @@ public class PgDialect implements Dialect { protected static final String HOST_ALIAS_QUERY = "SELECT pg_catalog.CONCAT(pg_catalog.inet_server_addr(), ':', pg_catalog.inet_server_port())"; + protected static final DialectUtils dialectUtils = new DialectUtils(); + private static PgExceptionHandler pgExceptionHandler; private static final EnumSet NO_FAILOVER_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); @@ -52,16 +54,7 @@ public class PgDialect implements Dialect { @Override public boolean isDialect(final Connection connection) { - try (Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery(PG_PROC_EXISTS_QUERY)) { - if (rs.next()) { - return true; - } - } catch (final SQLException ex) { - return false; - } - - return false; + return dialectUtils.checkExistenceQueries(connection, PG_PROC_EXISTS_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java index 2a0b1ed29..1e173c772 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsMysqlDialect.java @@ -31,8 +31,7 @@ public class RdsMysqlDialect extends MysqlDialect implements BlueGreenDialect { "SELECT 1 AS tmp FROM information_schema.tables WHERE" + " table_schema = 'mysql' AND table_name = 'rds_topology'"; - protected static final String BG_STATUS_QUERY = - "SELECT * FROM mysql.rds_topology"; + protected static final String BG_STATUS_QUERY = "SELECT * FROM mysql.rds_topology"; private static final List dialectUpdateCandidates = Arrays.asList( DialectCodes.AURORA_MYSQL, @@ -92,14 +91,7 @@ public List getDialectUpdateCandidates() { @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { - try { - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { - return rs.next(); - } - } catch (SQLException ex) { - return false; - } + return dialectUtils.checkExistenceQueries(connection, TOPOLOGY_TABLE_EXISTS_QUERY); } @Override diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java index 3ccad1a0f..62a52d019 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/RdsPgDialect.java @@ -80,14 +80,7 @@ public List getDialectUpdateCandidates() { @Override public boolean isBlueGreenStatusAvailable(final Connection connection) { - try { - try (Statement statement = connection.createStatement(); - ResultSet rs = statement.executeQuery(TOPOLOGY_TABLE_EXISTS_QUERY)) { - return rs.next(); - } - } catch (SQLException ex) { - return false; - } + return dialectUtils.checkExistenceQueries(connection, TOPOLOGY_TABLE_EXISTS_QUERY); } @Override From e57bf03435ef2fb037f5ba93e5615dd1633dc7a1 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 16:25:40 -0800 Subject: [PATCH 31/46] Change getInstanceId return value from Pair to String --- .../amazon/jdbc/PartialPluginService.java | 1 + .../amazon/jdbc/PluginServiceImpl.java | 1 + .../dialect/MultiAzClusterMysqlDialect.java | 7 ++---- .../jdbc/dialect/MultiAzClusterPgDialect.java | 4 +--- .../hostlistprovider/AuroraTopologyUtils.java | 2 +- .../GlobalAuroraTopologyUtils.java | 4 ++-- .../MultiAzTopologyUtils.java | 7 ++---- .../hostlistprovider/RdsHostListProvider.java | 9 ++++--- .../jdbc/hostlistprovider/TopologyUtils.java | 24 +++++-------------- .../ClusterTopologyMonitorImpl.java | 13 ++++------ .../bluegreen/BlueGreenInterimStatus.java | 1 + .../ClusterAwareWriterFailoverHandler.java | 1 + .../failover/FailoverConnectionPlugin.java | 1 + .../failover2/FailoverConnectionPlugin.java | 1 + .../limitless/LimitlessRouterMonitor.java | 1 + .../ReadWriteSplittingPlugin.java | 1 + .../plugin/staledns/AuroraStaleDnsHelper.java | 1 + 17 files changed, 32 insertions(+), 47 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 9828a2d25..078626d5b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -48,6 +48,7 @@ import software.amazon.jdbc.states.SessionStateService; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index cd6738b1a..025818cfd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -54,6 +54,7 @@ import software.amazon.jdbc.states.SessionStateServiceImpl; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 7ac08fc61..15ac76ad1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -44,11 +44,8 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzC + " table_schema = 'mysql' AND table_name = 'rds_topology'"; protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; - // This query returns both instanceId and instanceName. - // For example: "1845128080", "test-multiaz-instance-1" - private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING_INDEX(endpoint, '.', 1)" - + " FROM mysql.rds_topology" - + " WHERE id = @@server_id"; + protected static final String INSTANCE_ID_QUERY = + "SELECT SUBSTRING_INDEX(endpoint, '.', 1) FROM mysql.rds_topology WHERE id = @@server_id"; // For reader instances, this query returns a writer instance ID. For a writer instance, this query returns no data. protected static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; protected static final String WRITER_ID_QUERY_COLUMN_NAME = "Source_Server_Id"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 6750a3efd..958240032 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -38,9 +38,7 @@ public class MultiAzClusterPgDialect extends PgDialect implements MultiAzCluster protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - // This query returns both instanceId and instanceName. - // For example: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ", "test-multiaz-instance-1" - private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + protected static final String INSTANCE_ID_QUERY = "SELECT SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + " FROM rds_tools.show_topology()" + " WHERE id OPERATOR(pg_catalog.=) rds_tools.dbi_resource_id()"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java index f62415ff9..6661a997f 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -90,6 +90,6 @@ protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec i // Calculate weight based on instance lag in time and CPU utilization. final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); - return createHost(hostName, hostName, isWriter, weight, lastUpdateTime, initialHostSpec, instanceTemplate); + return createHost(hostName, isWriter, weight, lastUpdateTime, initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index e08d5877a..3e64e19fe 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -111,8 +111,8 @@ protected HostSpec createHost( "GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); } - return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, - instanceTemplate); + return createHost( + hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); } public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 2ba81b980..6170b391c 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -82,7 +82,6 @@ public boolean isWriterInstance(final Connection connection) throws SQLException return false; } - protected @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { @@ -114,10 +113,8 @@ protected HostSpec createHost( String endpoint = rs.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" - String hostId = rs.getString("id"); // "1034958454" - final boolean isWriter = hostId.equals(writerId); + final boolean isWriter = instanceName.equals(writerId); - return createHost( - hostId, instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); + return createHost(instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 4bd523e39..2b09f5acd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -310,8 +310,8 @@ public HostRole getHostRole(Connection conn) throws SQLException { public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { init(); try { - Pair instanceIds = this.topologyUtils.getInstanceId(connection); - if (instanceIds == null) { + String instanceId = this.topologyUtils.getInstanceId(connection); + if (instanceId == null) { throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection")); } @@ -326,10 +326,9 @@ public HostRole getHostRole(Connection conn) throws SQLException { return null; } - String instanceName = instanceIds.getValue2(); HostSpec foundHost = topology .stream() - .filter(host -> Objects.equals(instanceName, host.getHostId())) + .filter(host -> Objects.equals(instanceId, host.getHostId())) .findAny() .orElse(null); @@ -341,7 +340,7 @@ public HostRole getHostRole(Connection conn) throws SQLException { foundHost = topology .stream() - .filter(host -> Objects.equals(instanceName, host.getHostId())) + .filter(host -> Objects.equals(instanceId, host.getHostId())) .findAny() .orElse(null); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index b5a838bd1..a7053c2fe 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -126,14 +126,13 @@ protected int setNetworkTimeout(Connection conn) { public HostSpec createHost( String instanceId, - String instanceName, final boolean isWriter, final long weight, final Timestamp lastUpdateTime, final HostSpec initialHostSpec, final HostSpec instanceTemplate) { - instanceName = instanceName == null ? "?" : instanceName; - final String endpoint = instanceTemplate.getHost().replace("?", instanceName); + instanceId = instanceId == null ? "?" : instanceId; + final String endpoint = instanceTemplate.getHost().replace("?", instanceId); final int port = instanceTemplate.isPortSpecified() ? instanceTemplate.getPort() : initialHostSpec.getPort(); @@ -147,28 +146,17 @@ public HostSpec createHost( .weight(weight) .lastUpdateTime(lastUpdateTime) .build(); - hostSpec.addAlias(instanceName); - hostSpec.setHostId(instanceName); + hostSpec.addAlias(instanceId); + hostSpec.setHostId(instanceId); return hostSpec; } - /** - * Identifies instances across different database types using instanceId and instanceName values. - * - *

Database types handle these identifiers differently: - * - Aurora: Uses the instance name as both instanceId and instanceName - * Example: "test-instance-1" for both values - * - RDS Cluster: Uses distinct values for instanceId and instanceName - * Example: - * instanceId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" - * instanceName: "test-multiaz-instance-1" - */ - public @Nullable Pair getInstanceId(final Connection connection) { + public @Nullable String getInstanceId(final Connection connection) { try { try (final Statement stmt = connection.createStatement(); final ResultSet rs = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { if (rs.next()) { - return Pair.create(rs.getString(1), rs.getString(2)); + return rs.getString(1); } } } catch (SQLException ex) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 1c0f481f6..169aaa058 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -44,7 +44,6 @@ import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; -import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.ServiceUtility; @@ -512,11 +511,11 @@ protected List openAnyConnectionAndUpdateTopology() { "ClusterTopologyMonitorImpl.writerMonitoringConnection", new Object[] {this.writerHostSpec.get().getHost()})); } else { - final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); - if (pair != null) { - HostSpec instanceTemplate = this.getinstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); - HostSpec writerHost = this.topologyUtils.createHost( - pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, instanceTemplate); + final String instanceId = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); + if (instanceId != null) { + HostSpec instanceTemplate = this.getinstanceTemplate(instanceId, this.monitoringConnection.get()); + HostSpec writerHost = + this.topologyUtils.createHost(instanceId, true, 0, null, this.initialHostSpec, instanceTemplate); this.writerHostSpec.set(writerHost); LOGGER.finest(Messages.get( "ClusterTopologyMonitorImpl.writerMonitoringConnection", @@ -671,7 +670,6 @@ public void run() { } if (connection != null) { - boolean isWriter = false; try { isWriter = this.monitor.topologyUtils.isWriterInstance(connection); @@ -680,7 +678,6 @@ public void run() { "NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); throw new RuntimeException(ex); - } catch (SQLException ex) { this.monitor.closeConnection(connection); connection = null; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java index 622bc2eb0..3e2558839 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java @@ -23,6 +23,7 @@ import java.util.Set; import java.util.stream.Collectors; import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 1412d47e1..a6eafd1d3 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -37,6 +37,7 @@ import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.ExecutorFactory; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.ServiceUtility; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 3c830d75d..0eb2d9baa 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -47,6 +47,7 @@ import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 9e78c82e9..3c4e109a8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -47,6 +47,7 @@ import software.amazon.jdbc.plugin.failover.TransactionStateUnknownSQLException; import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java index ec350aab6..8c28b97b6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java @@ -26,6 +26,7 @@ import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index b44a2c927..6fff83f53 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -40,6 +40,7 @@ import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverSQLException; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.SqlState; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index f1d7b9114..a54e345d5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -31,6 +31,7 @@ import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.HostListProviderService; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; From 9ca3948d5d3cc774d3f69f87cb817a928af14534 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 16:41:21 -0800 Subject: [PATCH 32/46] Unit tests passing --- .../RdsHostListProviderTest.java | 526 +++++++++--------- 1 file changed, 262 insertions(+), 264 deletions(-) diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 29e0b55ca..a5b9f2fcb 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -1,264 +1,262 @@ -// /* -// * Copyright 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. -// * 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 software.amazon.jdbc.hostlistprovider; -// -// import static org.junit.jupiter.api.Assertions.assertEquals; -// import static org.junit.jupiter.api.Assertions.assertNotNull; -// import static org.junit.jupiter.api.Assertions.assertNull; -// import static org.junit.jupiter.api.Assertions.assertThrows; -// import static org.mockito.ArgumentMatchers.any; -// import static org.mockito.ArgumentMatchers.eq; -// import static org.mockito.Mockito.atMostOnce; -// import static org.mockito.Mockito.doReturn; -// import static org.mockito.Mockito.never; -// import static org.mockito.Mockito.times; -// import static org.mockito.Mockito.verify; -// import static org.mockito.Mockito.when; -// -// import java.sql.Connection; -// import java.sql.SQLException; -// import java.util.ArrayList; -// import java.util.Arrays; -// import java.util.Collections; -// import java.util.List; -// import java.util.Properties; -// import org.junit.jupiter.api.AfterEach; -// import org.junit.jupiter.api.BeforeEach; -// import org.junit.jupiter.api.Test; -// import org.mockito.ArgumentCaptor; -// import org.mockito.Captor; -// import org.mockito.Mock; -// import org.mockito.Mockito; -// import org.mockito.MockitoAnnotations; -// import software.amazon.jdbc.HostRole; -// import software.amazon.jdbc.HostSpec; -// import software.amazon.jdbc.HostSpecBuilder; -// import software.amazon.jdbc.PluginService; -// import software.amazon.jdbc.dialect.TopologyDialect; -// import software.amazon.jdbc.hostavailability.HostAvailability; -// import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; -// import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; -// import software.amazon.jdbc.util.FullServicesContainer; -// import software.amazon.jdbc.util.events.EventPublisher; -// import software.amazon.jdbc.util.storage.StorageService; -// import software.amazon.jdbc.util.storage.TestStorageServiceImpl; -// -// class RdsHostListProviderTest { -// private StorageService storageService; -// private RdsHostListProvider rdsHostListProvider; -// -// @Mock private Connection mockConnection; -// @Mock private FullServicesContainer mockServicesContainer; -// @Mock private PluginService mockPluginService; -// @Mock private HostListProviderService mockHostListProviderService; -// @Mock private HostSpecBuilder mockHostSpecBuilder; -// @Mock private EventPublisher mockEventPublisher; -// @Mock private TopologyUtils mockTopologyUtils; -// @Mock private TopologyDialect mockDialect; -// @Captor private ArgumentCaptor queryCaptor; -// -// private AutoCloseable closeable; -// private final HostSpec currentHostSpec = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) -// .host("foo").port(1234).build(); -// private final List hosts = Arrays.asList( -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host1").build(), -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host2").build()); -// -// @BeforeEach -// void setUp() throws SQLException { -// closeable = MockitoAnnotations.openMocks(this); -// storageService = new TestStorageServiceImpl(mockEventPublisher); -// when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); -// when(mockServicesContainer.getStorageService()).thenReturn(storageService); -// when(mockServicesContainer.getPluginService()).thenReturn(mockPluginService); -// when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); -// when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); -// when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); -// when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); -// when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); -// when(mockHostListProviderService.getHostSpecBuilder()) -// .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); -// when(mockHostListProviderService.getCurrentConnection()).thenReturn(mockConnection); -// } -// -// @AfterEach -// void tearDown() throws Exception { -// storageService.clearAll(); -// closeable.close(); -// } -// -// private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { -// RdsHostListProvider provider = new RdsHostListProvider( -// mockDialect, new Properties(), originalUrl, mockServicesContainer, mockTopologyUtils); -// provider.init(); -// return provider; -// } -// -// @Test -// void testGetTopology_returnCachedTopology() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("protocol://url/")); -// -// final List expected = hosts; -// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); -// -// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); -// assertEquals(expected, result.hosts); -// assertEquals(2, result.hosts.size()); -// verify(rdsHostListProvider, never()).queryForTopology(mockConnection); -// } -// -// @Test -// void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// rdsHostListProvider.isInitialized = true; -// -// storageService.set(rdsHostListProvider.clusterId, new Topology(hosts)); -// -// final List newHosts = Collections.singletonList( -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); -// doReturn(newHosts).when(mockTopologyUtils).queryForTopology(eq(mockConnection), any(HostSpec.class)); -// -// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); -// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); -// assertEquals(1, result.hosts.size()); -// assertEquals(newHosts, result.hosts); -// } -// -// @Test -// void testGetTopology_noForceUpdate_queryReturnsEmptyHostList() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// rdsHostListProvider.clusterId = "cluster-id"; -// rdsHostListProvider.isInitialized = true; -// -// final List expected = hosts; -// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); -// -// doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); -// -// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); -// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); -// assertEquals(2, result.hosts.size()); -// assertEquals(expected, result.hosts); -// } -// -// @Test -// void testGetTopology_withForceUpdate_returnsInitialHostList() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// rdsHostListProvider.clear(); -// -// doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); -// -// final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); -// verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); -// assertNotNull(result.hosts); -// assertEquals( -// Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("url").build()), -// result.hosts); -// } -// -// @Test -// void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { -// final List expectedMySQL = Collections.singletonList( -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("mysql").port(HostSpec.NO_PORT) -// .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); -// final List expectedPostgres = Collections.singletonList( -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) -// .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); -// when(mockTopologyUtils.queryForTopology(eq(mockConnection), any(HostSpec.class))).thenReturn(expectedMySQL).thenReturn(expectedPostgres); -// -// -// rdsHostListProvider = getRdsHostListProvider("mysql://url/"); -// -// List hosts = rdsHostListProvider.queryForTopology(mockConnection); -// assertEquals(expectedMySQL, hosts); -// -// rdsHostListProvider = getRdsHostListProvider("postgresql://url/"); -// hosts = rdsHostListProvider.queryForTopology(mockConnection); -// assertEquals(expectedPostgres, hosts); -// } -// -// @Test -// void testGetCachedTopology_returnStoredTopology() throws SQLException { -// rdsHostListProvider = getRdsHostListProvider("jdbc:someprotocol://url"); -// -// final List expected = hosts; -// storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); -// -// final List result = rdsHostListProvider.getStoredTopology(); -// assertEquals(expected, result); -// } -// -// @Test -// void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// -// assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); -// -// when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); -// assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); -// } -// -// @Test -// void testIdentifyConnectionNullTopology() throws SQLException { -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// rdsHostListProvider.clusterInstanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) -// .host("?.pattern").build(); -// -// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); -// doReturn(null).when(rdsHostListProvider).refresh(mockConnection); -// doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); -// -// assertNull(rdsHostListProvider.identifyConnection(mockConnection)); -// } -// -// @Test -// void testIdentifyConnectionHostNotInTopology() throws SQLException { -// final List cachedTopology = Collections.singletonList( -// new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) -// .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") -// .port(HostSpec.NO_PORT) -// .role(HostRole.WRITER) -// .build()); -// -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); -// doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); -// doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); -// -// assertNull(rdsHostListProvider.identifyConnection(mockConnection)); -// } -// -// @Test -// void testIdentifyConnectionHostInTopology() throws SQLException { -// final HostSpec expectedHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) -// .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") -// .port(HostSpec.NO_PORT) -// .role(HostRole.WRITER) -// .build(); -// expectedHost.setHostId("instance-a-1"); -// final List cachedTopology = Collections.singletonList(expectedHost); -// -// rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); -// when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); -// doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); -// doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); -// -// final HostSpec actual = rdsHostListProvider.identifyConnection(mockConnection); -// assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); -// assertEquals("instance-a-1", actual.getHostId()); -// } -// } +/* + * Copyright 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. + * 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 software.amazon.jdbc.hostlistprovider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atMostOnce; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import software.amazon.jdbc.HostRole; +import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.HostSpecBuilder; +import software.amazon.jdbc.PluginService; +import software.amazon.jdbc.dialect.TopologyDialect; +import software.amazon.jdbc.hostavailability.HostAvailability; +import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; +import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; +import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.events.EventPublisher; +import software.amazon.jdbc.util.storage.StorageService; +import software.amazon.jdbc.util.storage.TestStorageServiceImpl; + +class RdsHostListProviderTest { + private StorageService storageService; + private RdsHostListProvider rdsHostListProvider; + + @Mock private Connection mockConnection; + @Mock private FullServicesContainer mockServicesContainer; + @Mock private PluginService mockPluginService; + @Mock private HostListProviderService mockHostListProviderService; + @Mock private HostSpecBuilder mockHostSpecBuilder; + @Mock private EventPublisher mockEventPublisher; + @Mock private TopologyUtils mockTopologyUtils; + @Mock private TopologyDialect mockDialect; + + private AutoCloseable closeable; + private final HostSpec currentHostSpec = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) + .host("foo").port(1234).build(); + private final List hosts = Arrays.asList( + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host1").build(), + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("host2").build()); + + @BeforeEach + void setUp() throws SQLException { + closeable = MockitoAnnotations.openMocks(this); + storageService = new TestStorageServiceImpl(mockEventPublisher); + when(mockServicesContainer.getHostListProviderService()).thenReturn(mockHostListProviderService); + when(mockServicesContainer.getStorageService()).thenReturn(storageService); + when(mockServicesContainer.getPluginService()).thenReturn(mockPluginService); + when(mockPluginService.getCurrentConnection()).thenReturn(mockConnection); + when(mockPluginService.connect(any(HostSpec.class), any(Properties.class))).thenReturn(mockConnection); + when(mockPluginService.getCurrentHostSpec()).thenReturn(currentHostSpec); + when(mockPluginService.getHostSpecBuilder()).thenReturn(mockHostSpecBuilder); + when(mockHostListProviderService.getDialect()).thenReturn(mockDialect); + when(mockHostListProviderService.getHostSpecBuilder()) + .thenReturn(new HostSpecBuilder(new SimpleHostAvailabilityStrategy())); + when(mockHostListProviderService.getCurrentConnection()).thenReturn(mockConnection); + } + + @AfterEach + void tearDown() throws Exception { + storageService.clearAll(); + closeable.close(); + } + + private RdsHostListProvider getRdsHostListProvider(String originalUrl) throws SQLException { + RdsHostListProvider provider = new RdsHostListProvider( + mockTopologyUtils, new Properties(), originalUrl, mockServicesContainer); + provider.init(); + return provider; + } + + @Test + void testGetTopology_returnCachedTopology() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("protocol://url/")); + + final List expected = hosts; + storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); + + final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); + assertEquals(expected, result.hosts); + assertEquals(2, result.hosts.size()); + verify(rdsHostListProvider, never()).queryForTopology(mockConnection); + } + + @Test + void testGetTopology_withForceUpdate_returnsUpdatedTopology() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + rdsHostListProvider.isInitialized = true; + + storageService.set(rdsHostListProvider.clusterId, new Topology(hosts)); + + final List newHosts = Collections.singletonList( + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("newHost").build()); + doReturn(newHosts).when(mockTopologyUtils).queryForTopology( + eq(mockConnection), any(HostSpec.class), any(HostSpec.class)); + + final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); + verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); + assertEquals(1, result.hosts.size()); + assertEquals(newHosts, result.hosts); + } + + @Test + void testGetTopology_noForceUpdate_queryReturnsEmptyHostList() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + rdsHostListProvider.clusterId = "cluster-id"; + rdsHostListProvider.isInitialized = true; + + final List expected = hosts; + storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); + + doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); + + final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, false); + verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); + assertEquals(2, result.hosts.size()); + assertEquals(expected, result.hosts); + } + + @Test + void testGetTopology_withForceUpdate_returnsInitialHostList() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + rdsHostListProvider.clear(); + + doReturn(new ArrayList<>()).when(rdsHostListProvider).queryForTopology(mockConnection); + + final FetchTopologyResult result = rdsHostListProvider.getTopology(mockConnection, true); + verify(rdsHostListProvider, atMostOnce()).queryForTopology(mockConnection); + assertNotNull(result.hosts); + assertEquals( + Collections.singletonList(new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("url").build()), + result.hosts); + } + + @Test + void testQueryForTopology_withDifferentDriverProtocol() throws SQLException { + final List expectedMySQL = Collections.singletonList( + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("mysql").port(HostSpec.NO_PORT) + .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); + final List expectedPostgres = Collections.singletonList( + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("postgresql").port(HostSpec.NO_PORT) + .role(HostRole.WRITER).availability(HostAvailability.AVAILABLE).weight(0).build()); + when(mockTopologyUtils.queryForTopology(eq(mockConnection), any(HostSpec.class), any(HostSpec.class))) + .thenReturn(expectedMySQL).thenReturn(expectedPostgres); + + + rdsHostListProvider = getRdsHostListProvider("mysql://url/"); + + List hosts = rdsHostListProvider.queryForTopology(mockConnection); + assertEquals(expectedMySQL, hosts); + + rdsHostListProvider = getRdsHostListProvider("postgresql://url/"); + hosts = rdsHostListProvider.queryForTopology(mockConnection); + assertEquals(expectedPostgres, hosts); + } + + @Test + void testGetCachedTopology_returnStoredTopology() throws SQLException { + rdsHostListProvider = getRdsHostListProvider("jdbc:someprotocol://url"); + + final List expected = hosts; + storageService.set(rdsHostListProvider.clusterId, new Topology(expected)); + + final List result = rdsHostListProvider.getStoredTopology(); + assertEquals(expected, result); + } + + @Test + void testIdentifyConnectionWithInvalidNodeIdQuery() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + + assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); + + when(mockConnection.createStatement()).thenThrow(new SQLException("exception")); + assertThrows(SQLException.class, () -> rdsHostListProvider.identifyConnection(mockConnection)); + } + + @Test + void testIdentifyConnectionNullTopology() throws SQLException { + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + rdsHostListProvider.instanceTemplate = + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("?.pattern").build(); + + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); + doReturn(null).when(rdsHostListProvider).refresh(mockConnection); + doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); + + assertNull(rdsHostListProvider.identifyConnection(mockConnection)); + } + + @Test + void testIdentifyConnectionHostNotInTopology() throws SQLException { + final List cachedTopology = Collections.singletonList( + new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) + .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") + .port(HostSpec.NO_PORT) + .role(HostRole.WRITER) + .build()); + + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); + doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); + doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); + + assertNull(rdsHostListProvider.identifyConnection(mockConnection)); + } + + @Test + void testIdentifyConnectionHostInTopology() throws SQLException { + final HostSpec expectedHost = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()) + .host("instance-a-1.xyz.us-east-2.rds.amazonaws.com") + .port(HostSpec.NO_PORT) + .role(HostRole.WRITER) + .build(); + expectedHost.setHostId("instance-a-1"); + final List cachedTopology = Collections.singletonList(expectedHost); + + rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); + doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); + doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); + + final HostSpec actual = rdsHostListProvider.identifyConnection(mockConnection); + assertEquals("instance-a-1.xyz.us-east-2.rds.amazonaws.com", actual.getHost()); + assertEquals("instance-a-1", actual.getHostId()); + } +} From 2e154f8af4ff11bae4667a252403364bd0f050ed Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 17:11:10 -0800 Subject: [PATCH 33/46] Cleanup --- docs/development-guide/IntegrationTests.md | 12 ----------- .../software/amazon/jdbc/util/RdsUtils.java | 21 +++++++++---------- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/docs/development-guide/IntegrationTests.md b/docs/development-guide/IntegrationTests.md index 2dec6b0b1..62cf5168f 100644 --- a/docs/development-guide/IntegrationTests.md +++ b/docs/development-guide/IntegrationTests.md @@ -69,15 +69,3 @@ cmd /c ./gradlew --no-parallel --no-daemon test-all-environments ``` Test results can be found at `wrapper/build/report/index.html`. - -If you encounter unexplained build issues/errors, or after major project structure changes, try running the following to perform a clean build: - -macOS: -```bash -./gradlew clean -``` - -Windows: -```bash -cmd /c ./gradlew clean -``` diff --git a/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java b/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java index c1eec6545..1331e6dc4 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/util/RdsUtils.java @@ -93,7 +93,7 @@ public class RdsUtils { "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.(rds|rds-fips)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", + + "\\.rds\\.amazonaws\\.com\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_CLUSTER_PATTERN = @@ -101,21 +101,20 @@ public class RdsUtils { "^(?.+)\\." + "(?cluster-|cluster-ro-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.(rds|rds-fips)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", + + "\\.rds\\.amazonaws\\.com\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_LIMITLESS_CLUSTER_PATTERN = Pattern.compile( "(?.+)\\." + "(?shardgrp-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.(rds|rds-fips)\\.(amazonaws\\.com\\.?|amazonaws\\.eu\\.?|amazonaws\\.au\\.?|amazonaws\\.uk\\.?" - + "|amazonaws\\.com\\.cn\\.?|sc2s\\.sgov\\.gov\\.?|c2s\\.ic\\.gov\\.?))$", + + "\\.rds\\.(amazonaws\\.com\\.?|amazonaws\\.com\\.cn\\.?|sc2s\\.sgov\\.gov\\.?|c2s\\.ic\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_CHINA_DNS_PATTERN = Pattern.compile( "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" - + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + "\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); @@ -123,7 +122,7 @@ public class RdsUtils { Pattern.compile( "^(?.+)\\." + "(?cluster-|cluster-ro-)+" - + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + "\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); @@ -132,7 +131,7 @@ public class RdsUtils { "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.(rds|rds-fips)\\.amazonaws\\.com\\.cn\\.?)$", + + "\\.rds\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_OLD_CHINA_CLUSTER_PATTERN = @@ -140,14 +139,14 @@ public class RdsUtils { "^(?.+)\\." + "(?cluster-|cluster-ro-)+" + "(?[a-zA-Z0-9]+\\.(?[a-zA-Z0-9\\-]+)" - + "\\.(rds|rds-fips)\\.amazonaws\\.com\\.cn\\.?)$", + + "\\.rds\\.amazonaws\\.com\\.cn\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern AURORA_GOV_DNS_PATTERN = Pattern.compile( "^(?.+)\\." + "(?proxy-|cluster-|cluster-ro-|cluster-custom-|shardgrp-)?" - + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + "\\.(amazonaws\\.com\\.?|c2s\\.ic\\.gov\\.?|sc2s\\.sgov\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); @@ -155,14 +154,14 @@ public class RdsUtils { Pattern.compile( "^(?.+)\\." + "(?cluster-|cluster-ro-)+" - + "(?[a-zA-Z0-9]+\\.(rds|rds-fips)\\.(?[a-zA-Z0-9\\-]+)" + + "(?[a-zA-Z0-9]+\\.rds\\.(?[a-zA-Z0-9\\-]+)" + "\\.(amazonaws\\.com\\.?|c2s\\.ic\\.gov\\.?|sc2s\\.sgov\\.gov\\.?))$", Pattern.CASE_INSENSITIVE); private static final Pattern ELB_PATTERN = Pattern.compile( "^(?.+)\\.elb\\." - + "((?[a-zA-Z0-9\\-]+)\\.amazonaws\\.(com|au|eu|uk)\\.?)$", + + "((?[a-zA-Z0-9\\-]+)\\.amazonaws\\.com\\.?)$", Pattern.CASE_INSENSITIVE); private static final Pattern IP_V4 = From f89056911c1071bd68bf68d5692f9fa53915b8bb Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Wed, 5 Nov 2025 18:27:05 -0800 Subject: [PATCH 34/46] Update javadocs --- .../amazon/jdbc/dialect/DialectUtils.java | 8 +++ .../GlobalAuroraTopologyUtils.java | 6 +-- .../jdbc/hostlistprovider/TopologyUtils.java | 52 +++++++++++++++++-- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java index 79f3473b3..a09480cd7 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/DialectUtils.java @@ -22,6 +22,14 @@ import java.sql.Statement; public class DialectUtils { + /** + * Given a series of existence queries, returns true if they all execute successfully and contain at least one record. + * Otherwise, returns false. + * + * @param conn the connection to use for executing the queries. + * @param existenceQueries the queries to check for existing records. + * @return true if all queries execute successfully and return at least one record, false otherwise. + */ public boolean checkExistenceQueries(Connection conn, String... existenceQueries) { for (String existenceQuery : existenceQueries) { try (Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(existenceQuery)) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index 3e64e19fe..6dcd54b85 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -55,7 +55,7 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu public @Nullable List queryForTopology( Connection conn, HostSpec initialHostSpec, Map instanceTemplatesByRegion) throws SQLException { - int networkTimeout = setNetworkTimeout(conn); + int originalNetworkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); final ResultSet rs = stmt.executeQuery(this.dialect.getTopologyQuery())) { if (rs.getMetaData().getColumnCount() == 0) { @@ -68,8 +68,8 @@ public GlobalAuroraTopologyUtils(GlobalAuroraTopologyDialect dialect, HostSpecBu } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); + if (originalNetworkTimeout == 0 && !conn.isClosed()) { + conn.setNetworkTimeout(networkTimeoutExecutor, originalNetworkTimeout); } } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index a7053c2fe..6208c3b9a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -39,6 +39,11 @@ import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.SynchronousExecutor; +/** + * An abstract class defining utility methods that can be used to retrieve and process a variety of database topology + * information. This class can be overridden to define logic specific to various database engine deployments + * (e.g. Aurora, Multi-AZ, Global Aurora etc.). + */ public abstract class TopologyUtils { private static final Logger LOGGER = Logger.getLogger(TopologyUtils.class.getName()); protected static final int DEFAULT_QUERY_TIMEOUT_MS = 1000; @@ -54,9 +59,19 @@ public TopologyUtils( this.hostSpecBuilder = hostSpecBuilder; } + /** + * Query the database for information for each instance in the database topology. + * + * @param conn the connection to use to query the database. + * @param initialHostSpec the {@link HostSpec} that was used to initially connect. + * @param instanceTemplate the template {@link HostSpec} to use when constructing new {@link HostSpec} objects from + * the data returned by the topology query. + * @return a list of {@link HostSpec} objects representing the results of the topology query. + * @throws SQLException if an error occurs when executing the topology or processing the results. + */ public @Nullable List queryForTopology(Connection conn, HostSpec initialHostSpec, HostSpec instanceTemplate) throws SQLException { - int networkTimeout = setNetworkTimeout(conn); + int originalNetworkTimeout = setNetworkTimeout(conn); try (final Statement stmt = conn.createStatement(); final ResultSet rs = stmt.executeQuery(this.dialect.getTopologyQuery())) { if (rs.getMetaData().getColumnCount() == 0) { @@ -69,8 +84,8 @@ public TopologyUtils( } catch (final SQLSyntaxErrorException e) { throw new SQLException(Messages.get("TopologyUtils.invalidQuery"), e); } finally { - if (networkTimeout == 0 && !conn.isClosed()) { - conn.setNetworkTimeout(networkTimeoutExecutor, networkTimeout); + if (originalNetworkTimeout == 0 && !conn.isClosed()) { + conn.setNetworkTimeout(networkTimeoutExecutor, originalNetworkTimeout); } } } @@ -124,6 +139,17 @@ protected int setNetworkTimeout(Connection conn) { return hosts; } + /** + * Creates a {@link HostSpec} from the given topology information. + * + * @param instanceId the database instance identifier, e.g. "mydb-instance-1". + * @param isWriter true if this is a writer instance, false for reader. + * @param weight the instance weight for load balancing. + * @param lastUpdateTime the timestamp of the last update to this instance's information. + * @param initialHostSpec the original host specification used for connecting. + * @param instanceTemplate the template used to construct the new {@link HostSpec}. + * @return a {@link HostSpec} representing the given information. + */ public HostSpec createHost( String instanceId, final boolean isWriter, @@ -151,6 +177,12 @@ public HostSpec createHost( return hostSpec; } + /** + * Get the instance ID of the current connection. + * + * @param connection the connection to use to query the database. + * @return the instance ID of the current connection. + */ public @Nullable String getInstanceId(final Connection connection) { try { try (final Statement stmt = connection.createStatement(); @@ -166,8 +198,22 @@ public HostSpec createHost( return null; } + /** + * Evaluate whether the given connection is to a writer instance. + * + * @param connection the connection to evaluate. + * @return true if the connection is to a writer instance, false otherwise. + * @throws SQLException if an exception occurs when querying the database or processing the database response. + */ public abstract boolean isWriterInstance(Connection connection) throws SQLException; + /** + * Evaluate the database role of the given connection, either {@link HostRole#WRITER} or {@link HostRole#READER}. + * + * @param conn the connection to evaluate. + * @return the database role of the given connection. + * @throws SQLException if an exception occurs when querying the database or processing the database response. + */ public HostRole getHostRole(Connection conn) throws SQLException { try (final Statement stmt = conn.createStatement(); final ResultSet rs = stmt.executeQuery(this.dialect.getIsReaderQuery())) { From 56aa05919f6904b7c907a9cd9e25355bb30e914d Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:03:03 -0800 Subject: [PATCH 35/46] Revert "Change getInstanceId return value from Pair to String" This reverts commit e57bf03435ef2fb037f5ba93e5615dd1633dc7a1. --- .../amazon/jdbc/PartialPluginService.java | 1 - .../amazon/jdbc/PluginServiceImpl.java | 1 - .../dialect/MultiAzClusterMysqlDialect.java | 7 ++++-- .../jdbc/dialect/MultiAzClusterPgDialect.java | 4 +++- .../hostlistprovider/AuroraTopologyUtils.java | 2 +- .../GlobalAuroraTopologyUtils.java | 4 ++-- .../MultiAzTopologyUtils.java | 7 ++++-- .../hostlistprovider/RdsHostListProvider.java | 9 +++---- .../jdbc/hostlistprovider/TopologyUtils.java | 24 ++++++++++++------- .../ClusterTopologyMonitorImpl.java | 13 ++++++---- .../bluegreen/BlueGreenInterimStatus.java | 1 - .../ClusterAwareWriterFailoverHandler.java | 1 - .../failover/FailoverConnectionPlugin.java | 1 - .../failover2/FailoverConnectionPlugin.java | 1 - .../limitless/LimitlessRouterMonitor.java | 1 - .../ReadWriteSplittingPlugin.java | 1 - .../plugin/staledns/AuroraStaleDnsHelper.java | 1 - 17 files changed, 44 insertions(+), 35 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 078626d5b..9828a2d25 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -48,7 +48,6 @@ import software.amazon.jdbc.states.SessionStateService; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index 025818cfd..cd6738b1a 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -54,7 +54,6 @@ import software.amazon.jdbc.states.SessionStateServiceImpl; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 15ac76ad1..7ac08fc61 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -44,8 +44,11 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzC + " table_schema = 'mysql' AND table_name = 'rds_topology'"; protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM mysql.rds_topology"; - protected static final String INSTANCE_ID_QUERY = - "SELECT SUBSTRING_INDEX(endpoint, '.', 1) FROM mysql.rds_topology WHERE id = @@server_id"; + // This query returns both instanceId and instanceName. + // For example: "1845128080", "test-multiaz-instance-1" + private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING_INDEX(endpoint, '.', 1)" + + " FROM mysql.rds_topology" + + " WHERE id = @@server_id"; // For reader instances, this query returns a writer instance ID. For a writer instance, this query returns no data. protected static final String WRITER_ID_QUERY = "SHOW REPLICA STATUS"; protected static final String WRITER_ID_QUERY_COLUMN_NAME = "Source_Server_Id"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 958240032..6750a3efd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -38,7 +38,9 @@ public class MultiAzClusterPgDialect extends PgDialect implements MultiAzCluster protected static final String TOPOLOGY_QUERY = "SELECT id, endpoint, port FROM rds_tools.show_topology('aws_jdbc_driver-" + DriverInfo.DRIVER_VERSION + "')"; - protected static final String INSTANCE_ID_QUERY = "SELECT SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + // This query returns both instanceId and instanceName. + // For example: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ", "test-multiaz-instance-1" + private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + " FROM rds_tools.show_topology()" + " WHERE id OPERATOR(pg_catalog.=) rds_tools.dbi_resource_id()"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java index 6661a997f..f62415ff9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/AuroraTopologyUtils.java @@ -90,6 +90,6 @@ protected HostSpec createHost(ResultSet rs, HostSpec initialHostSpec, HostSpec i // Calculate weight based on instance lag in time and CPU utilization. final long weight = Math.round(instanceLag) * 100L + Math.round(cpuUtilization); - return createHost(hostName, isWriter, weight, lastUpdateTime, initialHostSpec, instanceTemplate); + return createHost(hostName, hostName, isWriter, weight, lastUpdateTime, initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index 6dcd54b85..1f8675339 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -111,8 +111,8 @@ protected HostSpec createHost( "GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); } - return createHost( - hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); + return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, + instanceTemplate); } public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 6170b391c..2ba81b980 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -82,6 +82,7 @@ public boolean isWriterInstance(final Connection connection) throws SQLException return false; } + protected @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { @@ -113,8 +114,10 @@ protected HostSpec createHost( String endpoint = rs.getString("endpoint"); // "instance-name.XYZ.us-west-2.rds.amazonaws.com" String instanceName = endpoint.substring(0, endpoint.indexOf(".")); // "instance-name" - final boolean isWriter = instanceName.equals(writerId); + String hostId = rs.getString("id"); // "1034958454" + final boolean isWriter = hostId.equals(writerId); - return createHost(instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); + return createHost( + hostId, instanceName, isWriter, 0, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); } } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java index 2b09f5acd..4bd523e39 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/RdsHostListProvider.java @@ -310,8 +310,8 @@ public HostRole getHostRole(Connection conn) throws SQLException { public @Nullable HostSpec identifyConnection(Connection connection) throws SQLException { init(); try { - String instanceId = this.topologyUtils.getInstanceId(connection); - if (instanceId == null) { + Pair instanceIds = this.topologyUtils.getInstanceId(connection); + if (instanceIds == null) { throw new SQLException(Messages.get("RdsHostListProvider.errorIdentifyConnection")); } @@ -326,9 +326,10 @@ public HostRole getHostRole(Connection conn) throws SQLException { return null; } + String instanceName = instanceIds.getValue2(); HostSpec foundHost = topology .stream() - .filter(host -> Objects.equals(instanceId, host.getHostId())) + .filter(host -> Objects.equals(instanceName, host.getHostId())) .findAny() .orElse(null); @@ -340,7 +341,7 @@ public HostRole getHostRole(Connection conn) throws SQLException { foundHost = topology .stream() - .filter(host -> Objects.equals(instanceId, host.getHostId())) + .filter(host -> Objects.equals(instanceName, host.getHostId())) .findAny() .orElse(null); } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index 6208c3b9a..d24f291b6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -152,13 +152,14 @@ protected int setNetworkTimeout(Connection conn) { */ public HostSpec createHost( String instanceId, + String instanceName, final boolean isWriter, final long weight, final Timestamp lastUpdateTime, final HostSpec initialHostSpec, final HostSpec instanceTemplate) { - instanceId = instanceId == null ? "?" : instanceId; - final String endpoint = instanceTemplate.getHost().replace("?", instanceId); + instanceName = instanceName == null ? "?" : instanceName; + final String endpoint = instanceTemplate.getHost().replace("?", instanceName); final int port = instanceTemplate.isPortSpecified() ? instanceTemplate.getPort() : initialHostSpec.getPort(); @@ -172,23 +173,28 @@ public HostSpec createHost( .weight(weight) .lastUpdateTime(lastUpdateTime) .build(); - hostSpec.addAlias(instanceId); - hostSpec.setHostId(instanceId); + hostSpec.addAlias(instanceName); + hostSpec.setHostId(instanceName); return hostSpec; } /** - * Get the instance ID of the current connection. + * Identifies instances across different database types using instanceId and instanceName values. * - * @param connection the connection to use to query the database. - * @return the instance ID of the current connection. + *

Database types handle these identifiers differently: + * - Aurora: Uses the instance name as both instanceId and instanceName + * Example: "test-instance-1" for both values + * - RDS Cluster: Uses distinct values for instanceId and instanceName + * Example: + * instanceId: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ" + * instanceName: "test-multiaz-instance-1" */ - public @Nullable String getInstanceId(final Connection connection) { + public @Nullable Pair getInstanceId(final Connection connection) { try { try (final Statement stmt = connection.createStatement(); final ResultSet rs = stmt.executeQuery(this.dialect.getInstanceIdQuery())) { if (rs.next()) { - return rs.getString(1); + return Pair.create(rs.getString(1), rs.getString(2)); } } } catch (SQLException ex) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 169aaa058..1c0f481f6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -44,6 +44,7 @@ import software.amazon.jdbc.util.FullServicesContainer; import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.RdsUtils; import software.amazon.jdbc.util.ServiceUtility; @@ -511,11 +512,11 @@ protected List openAnyConnectionAndUpdateTopology() { "ClusterTopologyMonitorImpl.writerMonitoringConnection", new Object[] {this.writerHostSpec.get().getHost()})); } else { - final String instanceId = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); - if (instanceId != null) { - HostSpec instanceTemplate = this.getinstanceTemplate(instanceId, this.monitoringConnection.get()); - HostSpec writerHost = - this.topologyUtils.createHost(instanceId, true, 0, null, this.initialHostSpec, instanceTemplate); + final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); + if (pair != null) { + HostSpec instanceTemplate = this.getinstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); + HostSpec writerHost = this.topologyUtils.createHost( + pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, instanceTemplate); this.writerHostSpec.set(writerHost); LOGGER.finest(Messages.get( "ClusterTopologyMonitorImpl.writerMonitoringConnection", @@ -670,6 +671,7 @@ public void run() { } if (connection != null) { + boolean isWriter = false; try { isWriter = this.monitor.topologyUtils.isWriterInstance(connection); @@ -678,6 +680,7 @@ public void run() { "NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); throw new RuntimeException(ex); + } catch (SQLException ex) { this.monitor.closeConnection(connection); connection = null; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java index 3e2558839..622bc2eb0 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java @@ -23,7 +23,6 @@ import java.util.Set; import java.util.stream.Collectors; import software.amazon.jdbc.HostSpec; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.StringUtils; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index a6eafd1d3..1412d47e1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -37,7 +37,6 @@ import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.ExecutorFactory; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.ServiceUtility; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 0eb2d9baa..3c830d75d 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -47,7 +47,6 @@ import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 3c4e109a8..9e78c82e9 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -47,7 +47,6 @@ import software.amazon.jdbc.plugin.failover.TransactionStateUnknownSQLException; import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java index 8c28b97b6..ec350aab6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java @@ -26,7 +26,6 @@ import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.util.FullServicesContainer; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index 6fff83f53..b44a2c927 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -40,7 +40,6 @@ import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverSQLException; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.SqlState; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index a54e345d5..f1d7b9114 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -31,7 +31,6 @@ import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.HostListProviderService; -import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; From cd09a2d229b910d2ff6754b8ca020317966bced3 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:14:07 -0800 Subject: [PATCH 36/46] cleanup --- .../java/software/amazon/jdbc/PartialPluginService.java | 1 + .../main/java/software/amazon/jdbc/PluginServiceImpl.java | 1 + .../amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java | 2 +- .../amazon/jdbc/dialect/MultiAzClusterPgDialect.java | 3 +-- .../jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java | 4 ++-- .../amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java | 1 - .../monitoring/ClusterTopologyMonitorImpl.java | 2 -- .../jdbc/plugin/bluegreen/BlueGreenInterimStatus.java | 2 +- .../plugin/failover/ClusterAwareWriterFailoverHandler.java | 1 + .../jdbc/plugin/failover/FailoverConnectionPlugin.java | 1 + .../jdbc/plugin/failover2/FailoverConnectionPlugin.java | 1 + .../jdbc/plugin/limitless/LimitlessRouterMonitor.java | 2 +- .../readwritesplitting/ReadWriteSplittingPlugin.java | 1 + .../amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java | 1 + .../jdbc/hostlistprovider/RdsHostListProviderTest.java | 7 ++++--- 15 files changed, 17 insertions(+), 13 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java index 9828a2d25..078626d5b 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PartialPluginService.java @@ -48,6 +48,7 @@ import software.amazon.jdbc.states.SessionStateService; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java index cd6738b1a..025818cfd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java @@ -54,6 +54,7 @@ import software.amazon.jdbc.states.SessionStateServiceImpl; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.storage.CacheMap; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java index 7ac08fc61..9fe7d3b03 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterMysqlDialect.java @@ -46,7 +46,7 @@ public class MultiAzClusterMysqlDialect extends MysqlDialect implements MultiAzC // This query returns both instanceId and instanceName. // For example: "1845128080", "test-multiaz-instance-1" - private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING_INDEX(endpoint, '.', 1)" + protected static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING_INDEX(endpoint, '.', 1)" + " FROM mysql.rds_topology" + " WHERE id = @@server_id"; // For reader instances, this query returns a writer instance ID. For a writer instance, this query returns no data. diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index 6750a3efd..ece40baf8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -40,10 +40,9 @@ public class MultiAzClusterPgDialect extends PgDialect implements MultiAzCluster // This query returns both instanceId and instanceName. // For example: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ", "test-multiaz-instance-1" - private static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + protected static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + " FROM rds_tools.show_topology()" + " WHERE id OPERATOR(pg_catalog.=) rds_tools.dbi_resource_id()"; - // For reader instances, this query should return a writer instance ID. // For a writer instance, this query should return no data. protected static final String WRITER_ID_QUERY = diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java index 1f8675339..4a5d5eeea 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/GlobalAuroraTopologyUtils.java @@ -111,8 +111,8 @@ protected HostSpec createHost( "GlobalAuroraTopologyMonitor.cannotFindRegionTemplate", new Object[] {awsRegion})); } - return createHost(hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, - instanceTemplate); + return createHost( + hostName, hostName, isWriter, weight, Timestamp.from(Instant.now()), initialHostSpec, instanceTemplate); } public @Nullable String getRegion(String instanceId, Connection conn) throws SQLException { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 2ba81b980..1c3cc80b1 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -82,7 +82,6 @@ public boolean isWriterInstance(final Connection connection) throws SQLException return false; } - protected @Nullable String getWriterId(Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 1c0f481f6..590d6d451 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -671,7 +671,6 @@ public void run() { } if (connection != null) { - boolean isWriter = false; try { isWriter = this.monitor.topologyUtils.isWriterInstance(connection); @@ -680,7 +679,6 @@ public void run() { "NodeMonitoringThread.invalidWriterQuery", new Object[] {ex.getMessage()})); throw new RuntimeException(ex); - } catch (SQLException ex) { this.monitor.closeConnection(connection); connection = null; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java index 622bc2eb0..4a49d57a6 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/bluegreen/BlueGreenInterimStatus.java @@ -23,8 +23,8 @@ import java.util.Set; import java.util.stream.Collectors; import software.amazon.jdbc.HostSpec; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.StringUtils; -import software.amazon.jdbc.util.Utils; public class BlueGreenInterimStatus { public BlueGreenPhase blueGreenPhase; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java index 1412d47e1..a6eafd1d3 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/ClusterAwareWriterFailoverHandler.java @@ -37,6 +37,7 @@ import software.amazon.jdbc.hostavailability.HostAvailability; import software.amazon.jdbc.util.ExecutorFactory; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; import software.amazon.jdbc.util.ServiceUtility; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java index 3c830d75d..0eb2d9baa 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover/FailoverConnectionPlugin.java @@ -47,6 +47,7 @@ import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java index 9e78c82e9..3c4e109a8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/failover2/FailoverConnectionPlugin.java @@ -47,6 +47,7 @@ import software.amazon.jdbc.plugin.failover.TransactionStateUnknownSQLException; import software.amazon.jdbc.plugin.staledns.AuroraStaleDnsHelper; import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java index ec350aab6..9264e1603 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/limitless/LimitlessRouterMonitor.java @@ -26,9 +26,9 @@ import org.checkerframework.checker.nullness.qual.NonNull; import software.amazon.jdbc.HostSpec; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.PropertyUtils; -import software.amazon.jdbc.util.Utils; import software.amazon.jdbc.util.monitoring.AbstractMonitor; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.telemetry.TelemetryContext; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java index b44a2c927..6fff83f53 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/readwritesplitting/ReadWriteSplittingPlugin.java @@ -40,6 +40,7 @@ import software.amazon.jdbc.hostlistprovider.HostListProviderService; import software.amazon.jdbc.plugin.AbstractConnectionPlugin; import software.amazon.jdbc.plugin.failover.FailoverSQLException; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.SqlState; import software.amazon.jdbc.util.Utils; diff --git a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java index f1d7b9114..a54e345d5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java +++ b/wrapper/src/main/java/software/amazon/jdbc/plugin/staledns/AuroraStaleDnsHelper.java @@ -31,6 +31,7 @@ import software.amazon.jdbc.NodeChangeOptions; import software.amazon.jdbc.PluginService; import software.amazon.jdbc.hostlistprovider.HostListProviderService; +import software.amazon.jdbc.util.LogUtils; import software.amazon.jdbc.util.Messages; import software.amazon.jdbc.util.RdsUrlType; import software.amazon.jdbc.util.RdsUtils; diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index a5b9f2fcb..7c15bc872 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -50,6 +50,7 @@ import software.amazon.jdbc.hostavailability.SimpleHostAvailabilityStrategy; import software.amazon.jdbc.hostlistprovider.RdsHostListProvider.FetchTopologyResult; import software.amazon.jdbc.util.FullServicesContainer; +import software.amazon.jdbc.util.Pair; import software.amazon.jdbc.util.events.EventPublisher; import software.amazon.jdbc.util.storage.StorageService; import software.amazon.jdbc.util.storage.TestStorageServiceImpl; @@ -216,7 +217,7 @@ void testIdentifyConnectionNullTopology() throws SQLException { rdsHostListProvider.instanceTemplate = new HostSpecBuilder(new SimpleHostAvailabilityStrategy()).host("?.pattern").build(); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn(Pair.create("instance-1", "instance-1")); doReturn(null).when(rdsHostListProvider).refresh(mockConnection); doReturn(null).when(rdsHostListProvider).forceRefresh(mockConnection); @@ -233,7 +234,7 @@ void testIdentifyConnectionHostNotInTopology() throws SQLException { .build()); rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn(Pair.create("instance-1", "instance-1")); doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); @@ -251,7 +252,7 @@ void testIdentifyConnectionHostInTopology() throws SQLException { final List cachedTopology = Collections.singletonList(expectedHost); rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn("instance-a-1"); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn(Pair.create("instance-1", "instance-1")); doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); From d0d49fe4bff105cd47c771dbccf6cc2a7269e0c7 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:50:03 -0800 Subject: [PATCH 37/46] Fix unit test --- .../software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java | 3 ++- .../amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java index ece40baf8..ba3600710 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MultiAzClusterPgDialect.java @@ -40,7 +40,8 @@ public class MultiAzClusterPgDialect extends PgDialect implements MultiAzCluster // This query returns both instanceId and instanceName. // For example: "db-WQFQKBTL2LQUPIEFIFBGENS4ZQ", "test-multiaz-instance-1" - protected static final String INSTANCE_ID_QUERY = "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + protected static final String INSTANCE_ID_QUERY = + "SELECT id, SUBSTRING(endpoint FROM 0 FOR POSITION('.' IN endpoint))" + " FROM rds_tools.show_topology()" + " WHERE id OPERATOR(pg_catalog.=) rds_tools.dbi_resource_id()"; // For reader instances, this query should return a writer instance ID. diff --git a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java index 7c15bc872..991c0734b 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java +++ b/wrapper/src/test/java/software/amazon/jdbc/hostlistprovider/RdsHostListProviderTest.java @@ -252,7 +252,7 @@ void testIdentifyConnectionHostInTopology() throws SQLException { final List cachedTopology = Collections.singletonList(expectedHost); rdsHostListProvider = Mockito.spy(getRdsHostListProvider("jdbc:someprotocol://url")); - when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn(Pair.create("instance-1", "instance-1")); + when(mockTopologyUtils.getInstanceId(mockConnection)).thenReturn(Pair.create("instance-a-1", "instance-a-1")); doReturn(cachedTopology).when(rdsHostListProvider).refresh(mockConnection); doReturn(cachedTopology).when(rdsHostListProvider).forceRefresh(mockConnection); From 274ca5557db208327e66c7ca4342be0f21e8c57f Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:50:17 -0800 Subject: [PATCH 38/46] Revert "ci: scope down GitHub Token permissions (#1571)" This reverts commit 8babd96b234c9538b88d3a75b35cbf915a19f0a3. --- .github/workflows/main.yml | 3 --- .github/workflows/maven_release.yml | 3 --- .github/workflows/maven_snapshot.yml | 3 --- .github/workflows/remove-old-artifacts.yml | 3 --- .github/workflows/run-hibernate-orm-tests.yml | 3 --- .github/workflows/run-standard-integration-tests.yml | 3 --- 6 files changed, 18 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5d780649a..d0325b382 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,9 +16,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: - contents: read - jobs: markdown-link-check: runs-on: ubuntu-latest diff --git a/.github/workflows/maven_release.yml b/.github/workflows/maven_release.yml index fa4d699a0..c5ad886ff 100644 --- a/.github/workflows/maven_release.yml +++ b/.github/workflows/maven_release.yml @@ -5,9 +5,6 @@ on: types: - published -permissions: - contents: read - jobs: ubuntu-latest-jdbc-wrapper-release-to-maven: name: 'Build And Release to Maven' diff --git a/.github/workflows/maven_snapshot.yml b/.github/workflows/maven_snapshot.yml index cde9b6337..fda0edcd3 100644 --- a/.github/workflows/maven_snapshot.yml +++ b/.github/workflows/maven_snapshot.yml @@ -6,9 +6,6 @@ on: - main workflow_dispatch: -permissions: - contents: read - jobs: ubuntu-latest-jdbc-wrapper-snapshot-to-maven: name: 'Build And Upload Snapshot to Maven' diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml index 0a1d96058..60e2408d3 100644 --- a/.github/workflows/remove-old-artifacts.yml +++ b/.github/workflows/remove-old-artifacts.yml @@ -5,9 +5,6 @@ on: # Every day at 1am - cron: '0 1 * * *' -permissions: - actions: write - jobs: remove-old-artifacts: runs-on: ubuntu-latest diff --git a/.github/workflows/run-hibernate-orm-tests.yml b/.github/workflows/run-hibernate-orm-tests.yml index e1138a27f..c6fc6d948 100644 --- a/.github/workflows/run-hibernate-orm-tests.yml +++ b/.github/workflows/run-hibernate-orm-tests.yml @@ -6,9 +6,6 @@ on: branches: - main -permissions: - contents: read - jobs: hibernate-integration-tests: name: 'Run Hibernate ORM integration tests' diff --git a/.github/workflows/run-standard-integration-tests.yml b/.github/workflows/run-standard-integration-tests.yml index 43309463f..1471e55da 100644 --- a/.github/workflows/run-standard-integration-tests.yml +++ b/.github/workflows/run-standard-integration-tests.yml @@ -12,9 +12,6 @@ on: - '**/release_draft.yml' - '**/maven*.yml' -permissions: - contents: read - jobs: standard-integration-tests: name: 'Run standard container integration tests' From 5b748ade9e1027fbdd4c5236006e7fbdc3656b19 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:51:07 -0800 Subject: [PATCH 39/46] Revert "add support of the following domains: eu, au, uk (#1587)" This reverts commit 54aa6324a2373e745e1a41823b20ad2109fdcb4e. --- .../software/amazon/jdbc/util/RdsUtilsTests.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java b/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java index a0b5090ab..9938dc2e6 100644 --- a/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java +++ b/wrapper/src/test/java/software/amazon/jdbc/util/RdsUtilsTests.java @@ -44,9 +44,6 @@ public class RdsUtilsTests { private static final String usEastRegionLimitlessDbShardGroup = "database-test-name.shardgrp-XYZ.us-east-2.rds.amazonaws.com"; - private static final String euRedshift = - "redshift-test-name.XYZ.eusc-de-east-1.rds.amazonaws.eu"; - private static final String chinaRegionCluster = "database-test-name.cluster-XYZ.rds.cn-northwest-1.amazonaws.com.cn"; private static final String chinaRegionClusterTrailingDot = @@ -142,7 +139,6 @@ public void testIsRdsDns() { assertFalse(target.isRdsDns(usEastRegionElbUrl)); assertFalse(target.isRdsDns(usEastRegionElbUrlTrailingDot)); assertTrue(target.isRdsDns(usEastRegionLimitlessDbShardGroup)); - assertTrue(target.isRdsDns(euRedshift)); assertTrue(target.isRdsDns(chinaRegionCluster)); assertTrue(target.isRdsDns(chinaRegionClusterTrailingDot)); @@ -220,9 +216,6 @@ public void testGetRdsInstanceHostPattern() { assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionProxy)); assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionCustomDomain)); assertEquals(oldChinaExpectedHostPattern, target.getRdsInstanceHostPattern(oldChinaRegionLimitlessDbShardGroup)); - - final String euRedshiftExpectedHostPattern = "?.XYZ.eusc-de-east-1.rds.amazonaws.eu"; - assertEquals(euRedshiftExpectedHostPattern, target.getRdsInstanceHostPattern(euRedshift)); } @Test @@ -235,7 +228,6 @@ public void testIsRdsClusterDns() { assertFalse(target.isRdsClusterDns(usEastRegionCustomDomain)); assertFalse(target.isRdsClusterDns(usEastRegionElbUrl)); assertFalse(target.isRdsClusterDns(usEastRegionLimitlessDbShardGroup)); - assertFalse(target.isRdsClusterDns(euRedshift)); assertTrue(target.isRdsClusterDns(usIsobEastRegionCluster)); assertTrue(target.isRdsClusterDns(usIsobEastRegionClusterReadOnly)); @@ -276,7 +268,6 @@ public void testIsWriterClusterDns() { assertFalse(target.isWriterClusterDns(usEastRegionCustomDomain)); assertFalse(target.isWriterClusterDns(usEastRegionElbUrl)); assertFalse(target.isWriterClusterDns(usEastRegionLimitlessDbShardGroup)); - assertFalse(target.isWriterClusterDns(euRedshift)); assertTrue(target.isWriterClusterDns(usIsobEastRegionCluster)); assertFalse(target.isWriterClusterDns(usIsobEastRegionClusterReadOnly)); @@ -317,7 +308,6 @@ public void testIsReaderClusterDns() { assertFalse(target.isReaderClusterDns(usEastRegionCustomDomain)); assertFalse(target.isReaderClusterDns(usEastRegionElbUrl)); assertFalse(target.isReaderClusterDns(usEastRegionLimitlessDbShardGroup)); - assertFalse(target.isReaderClusterDns(euRedshift)); assertFalse(target.isReaderClusterDns(usIsobEastRegionCluster)); assertTrue(target.isReaderClusterDns(usIsobEastRegionClusterReadOnly)); @@ -358,7 +348,6 @@ public void testIsLimitlessDbShardGroupDns() { assertFalse(target.isLimitlessDbShardGroupDns(usEastRegionCustomDomain)); assertFalse(target.isLimitlessDbShardGroupDns(usEastRegionElbUrl)); assertTrue(target.isLimitlessDbShardGroupDns(usEastRegionLimitlessDbShardGroup)); - assertFalse(target.isLimitlessDbShardGroupDns(euRedshift)); assertFalse(target.isLimitlessDbShardGroupDns(usIsobEastRegionCluster)); assertFalse(target.isLimitlessDbShardGroupDns(usIsobEastRegionClusterReadOnly)); @@ -434,9 +423,6 @@ public void testGetRdsRegion() { assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionProxy)); assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionCustomDomain)); assertEquals(chinaExpectedHostPattern, target.getRdsRegion(oldChinaRegionLimitlessDbShardGroup)); - - final String euRedshiftExpectedHostPattern = "eusc-de-east-1"; - assertEquals(euRedshiftExpectedHostPattern, target.getRdsRegion(euRedshift)); } @Test From b238463e7e65eb704ecd37c8110ebdfb3378b540 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 12:51:26 -0800 Subject: [PATCH 40/46] Revert "chore: fix standard integration tests (#1594)" This reverts commit 56b4476ce0ff67496b027ae2cd306f3f62a63659. --- wrapper/src/test/java/integration/host/TestEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrapper/src/test/java/integration/host/TestEnvironment.java b/wrapper/src/test/java/integration/host/TestEnvironment.java index 9fb6cac14..99fcd2985 100644 --- a/wrapper/src/test/java/integration/host/TestEnvironment.java +++ b/wrapper/src/test/java/integration/host/TestEnvironment.java @@ -1251,7 +1251,7 @@ private static void createTelemetryOtlpContainer(TestEnvironment env) { private static String getContainerBaseImageName(TestEnvironmentRequest request) { switch (request.getTargetJvm()) { case OPENJDK8: - return "amazoncorretto:8-alpine"; + return "openjdk:8-jdk-alpine"; case OPENJDK11: return "amazoncorretto:11.0.19-alpine3.17"; case OPENJDK17: From a460ec6dfcc80f800b7d02c57276dd5fe0b77ae6 Mon Sep 17 00:00:00 2001 From: Adnan Khan Date: Tue, 21 Oct 2025 18:20:12 -0400 Subject: [PATCH 41/46] ci: scope down GitHub Token permissions (#1571) --- .github/workflows/main.yml | 3 +++ .github/workflows/maven_release.yml | 3 +++ .github/workflows/maven_snapshot.yml | 3 +++ .github/workflows/remove-old-artifacts.yml | 3 +++ .github/workflows/run-hibernate-orm-tests.yml | 3 +++ .github/workflows/run-standard-integration-tests.yml | 3 +++ 6 files changed, 18 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d0325b382..5d780649a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,6 +16,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + jobs: markdown-link-check: runs-on: ubuntu-latest diff --git a/.github/workflows/maven_release.yml b/.github/workflows/maven_release.yml index c5ad886ff..fa4d699a0 100644 --- a/.github/workflows/maven_release.yml +++ b/.github/workflows/maven_release.yml @@ -5,6 +5,9 @@ on: types: - published +permissions: + contents: read + jobs: ubuntu-latest-jdbc-wrapper-release-to-maven: name: 'Build And Release to Maven' diff --git a/.github/workflows/maven_snapshot.yml b/.github/workflows/maven_snapshot.yml index fda0edcd3..cde9b6337 100644 --- a/.github/workflows/maven_snapshot.yml +++ b/.github/workflows/maven_snapshot.yml @@ -6,6 +6,9 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: ubuntu-latest-jdbc-wrapper-snapshot-to-maven: name: 'Build And Upload Snapshot to Maven' diff --git a/.github/workflows/remove-old-artifacts.yml b/.github/workflows/remove-old-artifacts.yml index 60e2408d3..0a1d96058 100644 --- a/.github/workflows/remove-old-artifacts.yml +++ b/.github/workflows/remove-old-artifacts.yml @@ -5,6 +5,9 @@ on: # Every day at 1am - cron: '0 1 * * *' +permissions: + actions: write + jobs: remove-old-artifacts: runs-on: ubuntu-latest diff --git a/.github/workflows/run-hibernate-orm-tests.yml b/.github/workflows/run-hibernate-orm-tests.yml index c6fc6d948..e1138a27f 100644 --- a/.github/workflows/run-hibernate-orm-tests.yml +++ b/.github/workflows/run-hibernate-orm-tests.yml @@ -6,6 +6,9 @@ on: branches: - main +permissions: + contents: read + jobs: hibernate-integration-tests: name: 'Run Hibernate ORM integration tests' diff --git a/.github/workflows/run-standard-integration-tests.yml b/.github/workflows/run-standard-integration-tests.yml index 1471e55da..43309463f 100644 --- a/.github/workflows/run-standard-integration-tests.yml +++ b/.github/workflows/run-standard-integration-tests.yml @@ -12,6 +12,9 @@ on: - '**/release_draft.yml' - '**/maven*.yml' +permissions: + contents: read + jobs: standard-integration-tests: name: 'Run standard container integration tests' From c0b3bd3383d6cad6b113f4020ff4baa2c02bdfe1 Mon Sep 17 00:00:00 2001 From: sergiyvamz <75754709+sergiyvamz@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:25:39 -0800 Subject: [PATCH 42/46] chore: fix standard integration tests (#1594) --- wrapper/src/test/java/integration/host/TestEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrapper/src/test/java/integration/host/TestEnvironment.java b/wrapper/src/test/java/integration/host/TestEnvironment.java index 99fcd2985..9fb6cac14 100644 --- a/wrapper/src/test/java/integration/host/TestEnvironment.java +++ b/wrapper/src/test/java/integration/host/TestEnvironment.java @@ -1251,7 +1251,7 @@ private static void createTelemetryOtlpContainer(TestEnvironment env) { private static String getContainerBaseImageName(TestEnvironmentRequest request) { switch (request.getTargetJvm()) { case OPENJDK8: - return "openjdk:8-jdk-alpine"; + return "amazoncorretto:8-alpine"; case OPENJDK11: return "amazoncorretto:11.0.19-alpine3.17"; case OPENJDK17: From e88543817cbbddb13495970319178ca7bf7c3ae3 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 15:44:16 -0800 Subject: [PATCH 43/46] Fix Aurora dialect instance ID queries --- .../java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java | 2 +- .../java/software/amazon/jdbc/dialect/AuroraPgDialect.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java index 7197d915a..8ae253cbd 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraMysqlDialect.java @@ -36,7 +36,7 @@ public class AuroraMysqlDialect extends MysqlDialect implements TopologyDialect, // filter out instances that have not been updated in the last 5 minutes + "WHERE time_to_sec(timediff(now(), LAST_UPDATE_TIMESTAMP)) <= 300 OR SESSION_ID = 'MASTER_SESSION_ID' "; - protected static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id"; + protected static final String INSTANCE_ID_QUERY = "SELECT @@aurora_server_id, @@aurora_server_id"; protected static final String WRITER_ID_QUERY = "SELECT SERVER_ID FROM information_schema.replica_host_status " + "WHERE SESSION_ID = 'MASTER_SESSION_ID' AND SERVER_ID = @@aurora_server_id"; diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java index db71e9b62..e43f30cb8 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/AuroraPgDialect.java @@ -49,7 +49,8 @@ public class AuroraPgDialect extends PgDialect implements TopologyDialect, Auror + "OR SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " + "OR LAST_UPDATE_TIMESTAMP IS NULL"; - protected static final String INSTANCE_ID_QUERY = "SELECT pg_catalog.aurora_db_instance_identifier()"; + protected static final String INSTANCE_ID_QUERY = + "SELECT pg_catalog.aurora_db_instance_identifier(), pg_catalog.aurora_db_instance_identifier()"; protected static final String WRITER_ID_QUERY = "SELECT SERVER_ID FROM pg_catalog.aurora_replica_status() " + "WHERE SESSION_ID OPERATOR(pg_catalog.=) 'MASTER_SESSION_ID' " From 2fab544d496c2de73bcda6be93535b7a122d9998 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Thu, 6 Nov 2025 17:21:52 -0800 Subject: [PATCH 44/46] Fix bug where isWriterInstance didn't work properly --- .../jdbc/hostlistprovider/MultiAzTopologyUtils.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java index 1c3cc80b1..1e1045002 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/MultiAzTopologyUtils.java @@ -69,17 +69,12 @@ public MultiAzTopologyUtils(MultiAzClusterDialect dialect, HostSpecBuilder hostS @Override public boolean isWriterInstance(final Connection connection) throws SQLException { try (final Statement stmt = connection.createStatement()) { + try (final ResultSet rs = stmt.executeQuery(this.dialect.getWriterIdQuery())) { - if (rs.next()) { - String instanceId = rs.getString(this.dialect.getWriterIdColumnName()); - // The writer ID is only returned when connected to a reader, so if the query does not return a value, it - // means we are connected to a writer. - return StringUtils.isNullOrEmpty(instanceId); - } + // When connected to a writer, the result is empty, otherwise it contains a single row. + return !rs.next(); } } - - return false; } protected @Nullable String getWriterId(Connection connection) throws SQLException { From 471c600d41c41bd44b4e274d66f6dbfca56c13bb Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 18 Nov 2025 14:57:31 -0800 Subject: [PATCH 45/46] PR comments --- .../main/java/software/amazon/jdbc/dialect/MysqlDialect.java | 4 +--- .../src/main/java/software/amazon/jdbc/dialect/PgDialect.java | 4 ++-- .../monitoring/ClusterTopologyMonitorImpl.java | 4 ++-- .../monitoring/GlobalAuroraTopologyMonitor.java | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java index f24ad1de8..e21bd06d5 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/MysqlDialect.java @@ -35,9 +35,6 @@ public class MysqlDialect implements Dialect { protected static final String VERSION_QUERY = "SHOW VARIABLES LIKE 'version_comment'"; protected static final String HOST_ALIAS_QUERY = "SELECT CONCAT(@@hostname, ':', @@port)"; - - protected static final DialectUtils dialectUtils = new DialectUtils(); - private static MySQLExceptionHandler mySQLExceptionHandler; private static final EnumSet NO_FAILOVER_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); @@ -48,6 +45,7 @@ public class MysqlDialect implements Dialect { DialectCodes.RDS_MYSQL ); + protected final DialectUtils dialectUtils = new DialectUtils(); @Override public boolean isDialect(final Connection connection) { diff --git a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java index 13031f012..abf33ee56 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java +++ b/wrapper/src/main/java/software/amazon/jdbc/dialect/PgDialect.java @@ -41,8 +41,6 @@ public class PgDialect implements Dialect { protected static final String HOST_ALIAS_QUERY = "SELECT pg_catalog.CONCAT(pg_catalog.inet_server_addr(), ':', pg_catalog.inet_server_port())"; - protected static final DialectUtils dialectUtils = new DialectUtils(); - private static PgExceptionHandler pgExceptionHandler; private static final EnumSet NO_FAILOVER_RESTRICTIONS = EnumSet.noneOf(FailoverRestriction.class); @@ -52,6 +50,8 @@ public class PgDialect implements Dialect { DialectCodes.RDS_MULTI_AZ_PG_CLUSTER, DialectCodes.RDS_PG); + protected final DialectUtils dialectUtils = new DialectUtils(); + @Override public boolean isDialect(final Connection connection) { return dialectUtils.checkExistenceQueries(connection, PG_PROC_EXISTS_QUERY); diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java index 590d6d451..5c142e498 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/ClusterTopologyMonitorImpl.java @@ -514,7 +514,7 @@ protected List openAnyConnectionAndUpdateTopology() { } else { final Pair pair = this.topologyUtils.getInstanceId(this.monitoringConnection.get()); if (pair != null) { - HostSpec instanceTemplate = this.getinstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); + HostSpec instanceTemplate = this.getInstanceTemplate(pair.getValue2(), this.monitoringConnection.get()); HostSpec writerHost = this.topologyUtils.createHost( pair.getValue1(), pair.getValue2(), true, 0, null, this.initialHostSpec, instanceTemplate); this.writerHostSpec.set(writerHost); @@ -556,7 +556,7 @@ protected List openAnyConnectionAndUpdateTopology() { return hosts; } - protected HostSpec getinstanceTemplate(String nodeId, Connection connection) throws SQLException { + protected HostSpec getInstanceTemplate(String nodeId, Connection connection) throws SQLException { return this.instanceTemplate; } diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java index cf5e20a7d..c280035d3 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/monitoring/GlobalAuroraTopologyMonitor.java @@ -56,7 +56,7 @@ public GlobalAuroraTopologyMonitor( } @Override - protected HostSpec getinstanceTemplate(String instanceId, Connection connection) throws SQLException { + protected HostSpec getInstanceTemplate(String instanceId, Connection connection) throws SQLException { String region = this.topologyUtils.getRegion(instanceId, connection); if (!StringUtils.isNullOrEmpty(region)) { final HostSpec instanceTemplate = this.instanceTemplatesByRegion.get(region); From 85efcf3b98d77a8889aa6269c316142f8c712340 Mon Sep 17 00:00:00 2001 From: aaron-congo Date: Tue, 18 Nov 2025 15:29:07 -0800 Subject: [PATCH 46/46] PR suggestions --- .../software/amazon/jdbc/hostlistprovider/TopologyUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java index d24f291b6..5a6a2ef68 100644 --- a/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java +++ b/wrapper/src/main/java/software/amazon/jdbc/hostlistprovider/TopologyUtils.java @@ -124,7 +124,6 @@ protected int setNetworkTimeout(Connection conn) { int writerCount = writers.size(); if (writerCount == 0) { - LOGGER.warning(() -> Messages.get("TopologyUtils.invalidTopology")); return null; } else if (writerCount == 1) { hosts.add(writers.get(0));