Skip to content

Granular SQL errors #6078

@vecerek

Description

@vecerek

What is the problem this feature would solve?

There are various reasons why a sql query may fail: ConnectTimeout, LockWaitTimeout, Deadlock, DuplicateEntry, SyntaxError, etc. Some errors are retriable (e.g. LockWaitTimeout), some are retriable with some modifications to the "query values" (e.g. DuplicateEntry), others aren't retriable at all.

Currently, the only way to distinguish the various types of errors is by doing something like this:

import type { QueryError } from "mysql2";

Effect.catchTag("SqlError", (error) => {
  if (error.cause instanceof Error) {
    const originalError = error.cause as QueryError;

    switch (originalError.code) {
      "ER_DUP_ENTRY": // ...
      "ER_LOCK_WAIT_TIMEOUT": // ...
      // etc.
      default: // ...
    }
  }
})

What is the feature you are proposing to solve the problem?

Extend the SqlError type with a code field, so that the block becomes a bit simpler:

Effect.catchTag("SqlError", (error) => {
  switch (error.code) {
    "ER_DUP_ENTRY": // ...
    "ER_LOCK_WAIT_TIMEOUT": // ...
    // etc.
    default: // ...
  }
})

The challenge is to pick the right type for code. It cannot simply be an enumeration such as:

type Code = "ER_DUP_ENTRY" | "ER_LOCK_WAIT_TIMEOUT" | ...;

These codes are not database-agnostic. For example, the duplicate entry key constraint violation error is called:

Typing it as string is more robust but lacks a good DX.
Another way to go would be to use the greatest common divisor approach, standardize some error codes within Effect SQL and perform some mapping within the sql-xyz libraries themselves.
Another option would be to ditch the codes but use SQLSTATE, a standardized five-character alphanumeric code used in database systems to identify the outcome of a SQL statement.

What alternatives have you considered?

I have considered two alternative solutions:

  1. Replacing SqlError with a union of its specific errors: DuplicateEntrySqlError | LockWaitTimeoutSqlError | ... | OtherSqlError. The problem with this approach would be too many error tags every time an Effect has to interact with the DB. Also, all these errors would appear on all queries regardless of their nature. For example, it is impossible to get a DuplicateEntrySqlError for a read-only query.
  2. Extending the SqlError type with a isRetriable boolean. The problem is that errors such as DuplicateEntry are not retriable without the right modifications to the query (maybe one of the ids has to be incremented, or so). In such case would it be a retriable or a non-retriable error? Maybe a retryStrategy: "without_modification" | "with_modification" | "never" might work better (I can't think of a better wording at the moment).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions