feat: support MySQL loadbalance:// and replication:// protocols with IAM authentication#1734
feat: support MySQL loadbalance:// and replication:// protocols with IAM authentication#1734sethusrinivasan wants to merge 2 commits into
Conversation
|
fixes #1589 |
…IAM authentication
When using the AWS Advanced JDBC Wrapper with MySQL's multi-host protocols
(loadbalance:// or replication://), the wrapper would decompose the multi-host
URL into individual single-host connections, breaking the MySQL driver's
internal load-balancing and replication routing. Additionally, the JDK dynamic
proxy connections created by MySQL's LoadBalancedConnectionProxy caused the
wrapper to incorrectly detect connection changes on every statement execution,
because Statement.getConnection() returns a different proxy object than the
original connection, and the wrapper compared them using object identity (!=).
This change fixes both issues to enable IAM database authentication with
MySQL's loadbalance:// protocol against Aurora MySQL clusters.
Code changes (4 files):
1. Driver.java: Detect multi-host protocols (loadbalance:, replication:) and
store the original hosts string in a new ORIGINAL_URL_HOSTS property so
it survives the wrapper's URL decomposition into individual HostSpecs.
2. PropertyDefinition.java: Add ORIGINAL_URL_HOSTS internal property
(wrapperOriginalUrlHosts) to carry the multi-host portion through the
connection pipeline.
3. MysqlConnectorJTargetDriverDialect.java: In prepareConnectInfo(), when
ORIGINAL_URL_HOSTS is present and the protocol is loadbalance:// or
replication://, reconstruct the full multi-host URL instead of using the
single HostSpec URL. The property is then stripped by removeAllExcept()
so it does not leak to the target driver.
4. WrapperUtils.java: Add isSameConnection() method used by
executeWithPlugins() to replace the != comparison for connection identity
checks. Handles three MySQL proxy patterns:
- Two proxies sharing the same InvocationHandler (same connection)
- Inner-class proxy detection via this$0 field (JdbcInterfaceProxy
referencing its enclosing LoadBalancedConnectionProxy)
- proxyWraps() checking invokeOn/thisAsConnection fields
- isWrapperFor fallback in both directions
Also changed isWrapperFor catch blocks from SQLException to Exception
to handle NullPointerException thrown by JDK dynamic proxies.
Test coverage (3 new test files, 28 tests total):
1. LoadBalanceProtocolTest.java (10 unit tests):
- URL parser extracts all hosts from loadbalance:// and replication:// URLs
- URL parser extracts correct protocol prefix
- MysqlConnectorJTargetDriverDialect preserves multi-host URL for both protocols
- Fallback to single-host URL when ORIGINAL_URL_HOSTS is absent
- Standard protocol ignores ORIGINAL_URL_HOSTS
- Driver.java host extraction logic for loadbalance and standard URLs
- CONNECTION_OBJECT_CHANGED detection behavior
2. WrapperUtilsIsSameConnectionTest.java (10 unit tests):
- Object identity (same reference)
- Different non-proxy connections
- Two proxies with shared/different InvocationHandlers
- Inner-class proxy detection (simulates MySQL's LoadBalancedConnectionProxy
+ JdbcInterfaceProxy pattern) in both directions
- isWrapperFor fallback (forward, reverse, exception handling)
- Proxy vs non-proxy mismatch
3. LoadBalanceIntegrationTest.java (8 integration tests against live Aurora MySQL):
- Basic wrapper connection via cluster endpoint
- loadbalance:// protocol connection with multiple instances
- 50-query stability test (no false connection-change detection)
- Load distribution across hosts (verifies >= 2 distinct hosts)
- Concurrent connection stress test (3 threads x 5 connections)
- IAM auth + loadbalance:// basic connectivity
- IAM auth + loadbalance:// multi-query stability
- IAM auth + loadbalance:// distribution across hosts
All 1794 unit tests pass (0 regressions). All 8 integration tests pass against
a live Aurora MySQL cluster with 2 serverless v2 instances. The only failure in
the full suite is the pre-existing flaky CustomEndpointMonitorImplTest.testRun().
Co-authored-by: Kiro AI with Claude Opus 4.6
d536608 to
f38ff44
Compare
|
Reviewed by AI
final String afterProtocol = driverUrl.substring(targetDriverProtocol.length()); The PR description mentions handling ? and # delimiters, but the code only checks for /. A URL like jdbc:mysql:loadbalance://host1:3306,host2:3306?useSSL=true (no Suggestion: Also check for ? and #: int endOfHosts = afterProtocol.length();
The executeWithPlugins method is the hot path — every JDBC call goes through it. The new isSameConnection method uses Proxy.isProxyClass(), Suggestion: Consider caching the result or at minimum short-circuiting earlier. The Proxy.isProxyClass() check is cheap, but the reflective field access
Java 9+ module system restrictions can cause setAccessible(true) to throw InaccessibleObjectException on fields in packages not opened to the unnamed module. MySQL final Field field = obj.getClass().getDeclaredField("this$0"); This is caught by the blanket catch (Exception e), so it won't crash, but it means the inner-class detection silently fails and falls through to isWrapperFor, which
Same concern as above — accessing invokeOn and thisAsConnection fields on MySQL Connector/J's internal handler classes. These are private fields in
In WrapperUtils.java, the new proxyWraps method ends immediately before the existing checkThrowable method with no blank line separator: Minor style issue but the project uses Google checkstyle which typically requires blank lines between methods.
Since AwsWrapperProperty fields are auto-registered via registerProperties, ORIGINAL_URL_HOSTS becomes a user-visible property. Users could set Suggestion: Consider validating that the property wasn't user-supplied, or use a non-AwsWrapperProperty mechanism (e.g., a separate internal-only property key that
LoadBalanceIntegrationTest.java is placed in wrapper/src/test/java/software/amazon/jdbc/ alongside unit tests. The project likely has a separate source set or
In MysqlConnectorJTargetDriverDialect: urlBuilder = protocol + originalHosts + "/" + databaseName; If databaseName is empty string (which it can be — the fallback is ""), this produces a trailing / like jdbc:mysql:loadbalance://host1,host2/. The original code path Positive Aspects
Verdict The core approach is correct and well-tested. The main concerns are:
|
…ve reflection
Move MySQL-specific connection identity logic out of WrapperUtils and
into the TargetDriverDialect hierarchy, following the project's existing
pattern for driver-specific behavior.
The original isSameConnection() in WrapperUtils used reflection
(getDeclaredField, setAccessible) to inspect MySQL Connector/J internal
proxy fields (this$0, invokeOn, thisAsConnection). The new implementation
uses JdbcConnection.getMultiHostSafeProxy(), a public MySQL API that
returns the same stable proxy object for both the original loadbalance
connection and any JdbcInterfaceProxy returned by Statement.getConnection().
Changes:
- TargetDriverDialect: add default isSameConnection() (identity check)
- MysqlConnectorJTargetDriverDialect: override to delegate to helper
- MysqlConnectorJDriverHelper: implement using JdbcConnection API, no reflection
- WrapperUtils: remove isSameConnection/getEnclosingInstance/proxyWraps
(136 lines), call dialect.isSameConnection() from executeWithPlugins
- WrapperUtilsTest: wire up GenericTargetDriverDialect mock
- Replace WrapperUtilsIsSameConnectionTest with IsSameConnectionTest
Summary
When using the AWS Advanced JDBC Wrapper with MySQL's multi-host protocols (loadbalance:// or replication://), the wrapper would decompose the multi-host URL into individual single-host connections, breaking the MySQL driver's internal load-balancing and replication routing. Additionally, the JDK dynamic proxy connections created by MySQL's LoadBalancedConnectionProxy caused the wrapper to incorrectly detect connection changes on every statement execution, because Statement.getConnection() returns a different proxy object than the original connection, and the wrapper compared them using object identity (!=).
Description
This change fixes both issues to enable IAM database authentication with MySQL's loadbalance:// protocol against Aurora MySQL clusters.
Code changes (4 files):
Driver.java: Detect multi-host protocols (loadbalance:, replication:) and store the original hosts string in a new ORIGINAL_URL_HOSTS property so it survives the wrapper's URL decomposition into individual HostSpecs.
PropertyDefinition.java: Add ORIGINAL_URL_HOSTS internal property (wrapperOriginalUrlHosts) to carry the multi-host portion through the connection pipeline.
MysqlConnectorJTargetDriverDialect.java: In prepareConnectInfo(), when ORIGINAL_URL_HOSTS is present and the protocol is loadbalance:// or replication://, reconstruct the full multi-host URL instead of using the single HostSpec URL. The property is then stripped by removeAllExcept() so it does not leak to the target driver.
WrapperUtils.java: Add isSameConnection() method used by executeWithPlugins() to replace the != comparison for connection identity checks. Handles three MySQL proxy patterns:
Test coverage (3 new test files, 28 tests total):
LoadBalanceProtocolTest.java (10 unit tests):
WrapperUtilsIsSameConnectionTest.java (10 unit tests):
LoadBalanceIntegrationTest.java (8 integration tests against live Aurora MySQL):
All 1794 unit tests pass (0 regressions). All 8 integration tests pass against a live Aurora MySQL cluster with 2 serverless v2 instances. The only failure in the full suite is the pre-existing flaky CustomEndpointMonitorImplTest.testRun().
Co-authored-by: Kiro AI with Claude Opus 4.6
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.