Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,22 @@ public boolean isLoginException(final String sqlState) {
return this.exceptionManager.isLoginException(this.dbDialect, sqlState);
}

@Override
public boolean isReadOnlyConnectionException(@Nullable String sqlState, @Nullable Integer errorCode) {
if (this.exceptionHandler != null) {
return this.exceptionHandler.isReadOnlyConnectionException(sqlState, errorCode);
}
return this.exceptionManager.isReadOnlyConnectionException(this.dbDialect, sqlState, errorCode);
}

@Override
public boolean isReadOnlyConnectionException(Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {
if (this.exceptionHandler != null) {
return this.exceptionHandler.isReadOnlyConnectionException(throwable, targetDriverDialect);
}
return this.exceptionManager.isReadOnlyConnectionException(this.dbDialect, throwable, targetDriverDialect);
}

@Override
public Dialect getDialect() {
return this.dbDialect;
Expand Down
16 changes: 16 additions & 0 deletions wrapper/src/main/java/software/amazon/jdbc/PluginServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -690,6 +690,22 @@ public boolean isLoginException(final String sqlState) {
return this.exceptionManager.isLoginException(this.dialect, sqlState);
}

@Override
public boolean isReadOnlyConnectionException(@Nullable String sqlState, @Nullable Integer errorCode) {
if (this.exceptionHandler != null) {
return this.exceptionHandler.isReadOnlyConnectionException(sqlState, errorCode);
}
return this.exceptionManager.isReadOnlyConnectionException(this.dialect, sqlState, errorCode);
}

@Override
public boolean isReadOnlyConnectionException(Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {
if (this.exceptionHandler != null) {
return this.exceptionHandler.isReadOnlyConnectionException(throwable, targetDriverDialect);
}
return this.exceptionManager.isReadOnlyConnectionException(this.dialect, throwable, targetDriverDialect);
}

@Override
public Dialect getDialect() {
return this.dialect;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
import software.amazon.jdbc.util.StringUtils;

public abstract class AbstractPgExceptionHandler implements ExceptionHandler {

protected static final String READ_ONLY_CONNECTION_SQLSTATE = "25006";

public abstract List<String> getNetworkErrors();

public abstract List<String> getAccessErrors();
Expand Down Expand Up @@ -95,4 +98,36 @@ public boolean isLoginException(final String sqlState) {
}
return getAccessErrors().contains(sqlState);
}

@Override
public boolean isReadOnlyConnectionException(
final @Nullable String sqlState, final @Nullable Integer errorCode) {
return READ_ONLY_CONNECTION_SQLSTATE.equals(sqlState);
}

@Override
public boolean isReadOnlyConnectionException(
final Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {

Throwable exception = throwable;

while (exception != null) {
String sqlState = null;
Integer errorCode = null;
if (exception instanceof SQLException) {
sqlState = ((SQLException) exception).getSQLState();
errorCode = ((SQLException) exception).getErrorCode();
} else if (targetDriverDialect != null) {
sqlState = targetDriverDialect.getSQLState(exception);
}

if (isReadOnlyConnectionException(sqlState, errorCode)) {
return true;
}

exception = exception.getCause();
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ public interface ExceptionHandler {
boolean isLoginException(String sqlState);

boolean isLoginException(Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect);

boolean isReadOnlyConnectionException(final @Nullable String sqlState, final @Nullable Integer errorCode);

boolean isReadOnlyConnectionException(Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package software.amazon.jdbc.exceptions;

import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.jdbc.Driver;
import software.amazon.jdbc.dialect.Dialect;
import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect;
Expand Down Expand Up @@ -44,6 +45,18 @@ public boolean isNetworkException(final Dialect dialect, final String sqlState)
return handler.isNetworkException(sqlState);
}

public boolean isReadOnlyConnectionException(
final Dialect dialect, final Throwable throwable, final TargetDriverDialect targetDriverDialect) {
final ExceptionHandler handler = getHandler(dialect);
return handler.isReadOnlyConnectionException(throwable, targetDriverDialect);
}

public boolean isReadOnlyConnectionException(
final Dialect dialect, final @Nullable String sqlState, final @Nullable Integer errorCode) {
final ExceptionHandler handler = getHandler(dialect);
return handler.isReadOnlyConnectionException(sqlState, errorCode);
}

private ExceptionHandler getHandler(final Dialect dialect) {
final ExceptionHandler customHandler = Driver.getCustomExceptionHandler();
return customHandler != null ? customHandler : dialect.getExceptionHandler();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public boolean isLoginException(final Throwable throwable, TargetDriverDialect t
if (exception instanceof SQLException) {
sqlState = ((SQLException) exception).getSQLState();
} else if (targetDriverDialect != null) {
sqlState = targetDriverDialect.getSQLState(throwable);
sqlState = targetDriverDialect.getSQLState(exception);
}

if (isLoginException(sqlState)) {
Expand All @@ -103,4 +103,15 @@ public boolean isLoginException(final Throwable throwable, TargetDriverDialect t
public boolean isLoginException(final String sqlState) {
return ACCESS_ERRORS.contains(sqlState);
}

@Override
public boolean isReadOnlyConnectionException(@Nullable String sqlState, @Nullable Integer errorCode) {
return false;
}

@Override
public boolean isReadOnlyConnectionException(
Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
package software.amazon.jdbc.exceptions;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect;
import software.amazon.jdbc.util.StringUtils;
Expand All @@ -27,6 +30,11 @@ public class MySQLExceptionHandler implements ExceptionHandler {
public static final String SET_NETWORK_TIMEOUT_ON_CLOSED_CONNECTION =
"setNetworkTimeout cannot be called on a closed connection";

private static final Set<Integer> SQLSTATE_READ_ONLY_CONNECTION = new HashSet<>(Arrays.asList(
1290, // The MySQL server is running with the --read-only option, so it cannot execute this statement
1836 // Running in read-only mode
));

@Override
public boolean isNetworkException(final Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {
Throwable exception = throwable;
Expand Down Expand Up @@ -81,7 +89,7 @@ public boolean isLoginException(final Throwable throwable, @Nullable TargetDrive
if (exception instanceof SQLException) {
sqlState = ((SQLException) exception).getSQLState();
} else if (targetDriverDialect != null) {
sqlState = targetDriverDialect.getSQLState(throwable);
sqlState = targetDriverDialect.getSQLState(exception);
}

if (isLoginException(sqlState)) {
Expand All @@ -103,6 +111,39 @@ public boolean isLoginException(final String sqlState) {
return SQLSTATE_ACCESS_ERROR.equals(sqlState);
}

@Override
public boolean isReadOnlyConnectionException(
final @Nullable String sqlState, final @Nullable Integer errorCode) {
// HY000 - generic SQL state; use error code for more specific information
return "HY000".equals(sqlState) && errorCode != null && (SQLSTATE_READ_ONLY_CONNECTION.contains(errorCode));
}

@Override
public boolean isReadOnlyConnectionException(
final Throwable throwable, @Nullable TargetDriverDialect targetDriverDialect) {

Throwable exception = throwable;

while (exception != null) {
String sqlState = null;
Integer errorCode = null;
if (exception instanceof SQLException) {
sqlState = ((SQLException) exception).getSQLState();
errorCode = ((SQLException) exception).getErrorCode();
} else if (targetDriverDialect != null) {
sqlState = targetDriverDialect.getSQLState(exception);
}

if (isReadOnlyConnectionException(sqlState, errorCode)) {
return true;
}

exception = exception.getCause();
}

return false;
}

private boolean isHikariMariaDbNetworkException(final SQLException sqlException) {
return sqlException.getSQLState().equals(SQLSTATE_SYNTAX_ERROR_OR_ACCESS_VIOLATION)
&& sqlException.getMessage().contains(SET_NETWORK_TIMEOUT_ON_CLOSED_CONNECTION);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,14 @@ protected boolean shouldExceptionTriggerConnectionSwitch(final Throwable t) {
return false;
}

return this.pluginService.isNetworkException(t, this.pluginService.getTargetDriverDialect());
if (this.pluginService.isNetworkException(t, this.pluginService.getTargetDriverDialect())) {
return true;
}

// For STRICT_WRITER failover mode when connection exception indicate that the connection's in read-only mode,
// initiate a failover by returning true.
return this.failoverMode == FailoverMode.STRICT_WRITER
&& this.pluginService.isReadOnlyConnectionException(t, this.pluginService.getTargetDriverDialect());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,14 @@ protected boolean shouldExceptionTriggerConnectionSwitch(final Throwable t) {
return false;
}

return this.pluginService.isNetworkException(t, this.pluginService.getTargetDriverDialect());
if (this.pluginService.isNetworkException(t, this.pluginService.getTargetDriverDialect())) {
return true;
}

// For STRICT_WRITER failover mode when connection exception indicate that the connection's in read-only mode,
// initiate a failover by returning true.
return this.failoverMode == FailoverMode.STRICT_WRITER
&& this.pluginService.isReadOnlyConnectionException(t, this.pluginService.getTargetDriverDialect());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
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.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
Expand Down Expand Up @@ -441,4 +444,17 @@ private void initializePlugin() throws SQLException {
spyPlugin.setReaderFailoverHandler(mockReaderFailoverHandler);
// doReturn(mockConnectionService).when(spyPlugin).getConnectionService();
}

@Test
void test_failover_when_read_only_connection() throws SQLException {
initializePlugin();
spyPlugin.failoverMode = FailoverMode.STRICT_WRITER;

when(mockPluginService.isReadOnlyConnectionException(any(), any(TargetDriverDialect.class))).thenReturn(true);
assertTrue(spyPlugin.shouldExceptionTriggerConnectionSwitch(new SQLException("test", "any")));

when(mockPluginService.isReadOnlyConnectionException(any(), any(TargetDriverDialect.class))).thenReturn(false);
assertFalse(spyPlugin.shouldExceptionTriggerConnectionSwitch(new SQLException("test", "any")));
}

}
Loading