Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow access to io.r2dbc.postgresql.ExceptionFactory exceptions from client code #644

Open
loehnertz opened this issue Mar 20, 2024 · 5 comments
Labels
status: waiting-for-feedback We need additional information before we can continue type: enhancement A general enhancement

Comments

@loehnertz
Copy link

loehnertz commented Mar 20, 2024

Feature Request

Is your feature request related to a problem? Please describe

When using this driver via Spring, when exceptions that this driver throws occur, Spring wraps them into wrapper exceptions such that different drivers can be used (e.g., JPA, Hibernate, R2DBC, etc.).

My example is the org.springframework.dao.DataIntegrityViolationException (the name implies when it occurs), which wraps around a Throwable cause that the driver threw. When using this R2DBC driver, that cause is io.r2dbc.postgresql.ExceptionFactory.PostgresqlDataIntegrityViolationException.

My use case was to access which constraint was violated in an UPDATE query to rethrow the exception with a specific human-readable error message (instead of violates unique constraint \"%s\").
Sadly, since all the exceptions produced by the ExceptionFactory are non-public, I cannot import them into my client code to perform a cast of the Throwable cause of Spring's exception to then access the ErrorDetails of the driver's exception which would contain the constraint that was violated which I am after, such as:

catch (org.springframework.dao.DataIntegrityViolationException exception) {
  Throwable cause = exception.getCause();
  // The below line does not compile as the exception cannot be imported or otherwise accessed.
  if (cause instanceof io.r2dbc.postgresql.ExceptionFactory.PostgresqlDataIntegrityViolationException) {
   if (cause.getErrorDetails().getConstraintName().map(cn -> cn.equals("only_one_version_enabled_idx")).orElse(false)) {
      throw new IllegalArgumentException("Only one version can be active at a time", exception);
   }
  }
  throw exception;
}

Unfortunately, this code does not compile.

Describe the solution you'd like

If the exceptions produced by io.r2dbc.postgresql.ExceptionFactory were public, the above example code would work.

For instance, Hibernate or JPA do support this "correctly" via e.g., HibernateExceptionTranslator that will (to stay with my example) bubble up a org.hibernate.exception.ConstraintViolationException as the Throwable cause of Spring's wrapper exception which is public, thus making this code compile (in current Java versions):

catch (org.springframework.dao.DataIntegrityViolationException exception) {
  Throwable cause = exception.getCause();
  if (cause instanceof org.hibernate.exception.ConstraintViolationException) {
   if ("only_one_version_enabled_idx".equals(cause.getConstraintName())) {
      throw new IllegalArgumentException("Only one version can be active at a time", exception);
   }
  }
  throw exception;
}

Describe alternatives you've considered

N/A

Teachability, Documentation, Adoption, Migration Strategy

N/A

@loehnertz loehnertz added the type: enhancement A general enhancement label Mar 20, 2024
@mp911de
Copy link
Collaborator

mp911de commented Apr 8, 2024

All exceptions implement PostgresqlException exposing ErrorDetails. Have you tried casting the underlying exception into PostgresqlException?

@mp911de mp911de added the status: waiting-for-feedback We need additional information before we can continue label Apr 8, 2024
@aelfric
Copy link

aelfric commented Nov 26, 2024

A related request - I wanted to be able to test that my retry logic correctly handles PostgresqlTransientException but since that class is package private, I cannot simulate that exception and there is no way to test the logic. Are there any testing utils that could be exposed to simulate the behavior of the ExceptionFactory?

@mp911de
Copy link
Collaborator

mp911de commented Nov 27, 2024

Generally speaking, PostgresqlTransientException is an implementation detail. We recommend using R2DBC SPI exceptions such as R2dbcTransientException. Would that work for you @aelfric?

@aelfric
Copy link

aelfric commented Nov 27, 2024

Sorry, I wasn't clear. I don't want to use the internal class in my retry condition. I am using the exception from the SPI project for that. But in my test, I want to be able to confirm that certain SQL error codes do in fact get turned into the R2dbcTransientException and get retried. If I just mock the SPI exception, then it becomes a very shallow test.

@mp911de
Copy link
Collaborator

mp911de commented Nov 27, 2024

But in my test, I want to be able to confirm that certain SQL error codes do in fact get turned into the R2dbcTransientException

Not quite sure I follow: Do you want to verify ErrorDetails.getCode() or do you want to test that io.r2dbc.postgresql.ExceptionFactory is creating the right exception from a given ErrorDetails?

Currently, the combination that a SPI exception can implement PostgresqlException cannot be easily mocked.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: waiting-for-feedback We need additional information before we can continue type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

3 participants