diff --git a/mysql/lib/mysql_error_handler.ts b/mysql/lib/mysql_error_handler.ts index 1713339d..9feb4fc2 100644 --- a/mysql/lib/mysql_error_handler.ts +++ b/mysql/lib/mysql_error_handler.ts @@ -71,11 +71,9 @@ export class MySQLErrorHandler implements ErrorHandler { } isReadOnlyConnectionError(e: Error): boolean { - if (Object.prototype.hasOwnProperty.call(e, "errno")) { - // @ts-ignore - return MySQLErrorHandler.READ_ONLY_ERROR_CODES.includes(e["errno"]); - } - return false; + // The driver error reaches the plugins wrapped in an AwsWrapperError, which keeps it as `cause`. + const errno = (e as { errno?: number }).errno ?? (e.cause as { errno?: number })?.errno; + return MySQLErrorHandler.READ_ONLY_ERROR_CODES.includes(errno); } hasLoginError(): boolean { diff --git a/pg/lib/abstract_pg_error_handler.ts b/pg/lib/abstract_pg_error_handler.ts index f13d6346..ca78431c 100644 --- a/pg/lib/abstract_pg_error_handler.ts +++ b/pg/lib/abstract_pg_error_handler.ts @@ -74,11 +74,9 @@ export abstract class AbstractPgErrorHandler implements ErrorHandler { } isReadOnlyConnectionError(e: Error): boolean { - if (Object.prototype.hasOwnProperty.call(e, "code")) { - // @ts-ignore - return AbstractPgErrorHandler.READ_ONLY_CONNECTION_SQLSTATE === e["code"]; - } - return false; + // pg hands the driver error over as-is today; the `cause` lookup covers it being wrapped later. + const code = (e as { code?: string }).code ?? (e.cause as { code?: string })?.code; + return code === AbstractPgErrorHandler.READ_ONLY_CONNECTION_SQLSTATE; } hasLoginError(): boolean { diff --git a/tests/unit/error_handler.test.ts b/tests/unit/error_handler.test.ts index 12b57fc2..31d46ce5 100644 --- a/tests/unit/error_handler.test.ts +++ b/tests/unit/error_handler.test.ts @@ -16,40 +16,60 @@ import { MySQLErrorHandler } from "../../mysql/lib/mysql_error_handler"; import { PgErrorHandler } from "../../pg/lib/pg_error_handler"; +import { AwsWrapperError } from "../../common/lib/utils/errors"; function errorWith(props: Record): Error { return Object.assign(new Error("test"), props); } +function asAwsWrapperError(error: Error): Error { + return new AwsWrapperError(error.message, error); +} + describe("test read only connection error", () => { - const pgHandler = new PgErrorHandler(); - const mysqlHandler = new MySQLErrorHandler(); + // mysql2 errors reach the plugins wrapped by ClientUtils, keeping the driver error as `cause`. + describe("mysql", () => { + const handler = new MySQLErrorHandler(); - it("test pg read only detected by sqlstate 25006", () => { - expect(pgHandler.isReadOnlyConnectionError(errorWith({ code: "25006" }))).toBe(true); - }); + it("test read only errno 1290 detected", () => { + expect(handler.isReadOnlyConnectionError(asAwsWrapperError(errorWith({ errno: 1290 })))).toBe(true); + }); - it("test pg unrelated sqlstate not detected", () => { - expect(pgHandler.isReadOnlyConnectionError(errorWith({ code: "42601" }))).toBe(false); - }); + it("test read only errno 1836 detected", () => { + expect(handler.isReadOnlyConnectionError(asAwsWrapperError(errorWith({ errno: 1836 })))).toBe(true); + }); - it("test pg error without code not detected", () => { - expect(pgHandler.isReadOnlyConnectionError(new Error("cannot execute in a read-only transaction"))).toBe(false); - }); + it("test unrelated errno not detected", () => { + expect(handler.isReadOnlyConnectionError(asAwsWrapperError(errorWith({ errno: 1064 })))).toBe(false); + }); - it("test mysql read only detected by errno 1290", () => { - expect(mysqlHandler.isReadOnlyConnectionError(errorWith({ errno: 1290 }))).toBe(true); - }); + it("test error without errno not detected", () => { + expect(handler.isReadOnlyConnectionError(asAwsWrapperError(new Error("read only")))).toBe(false); + }); - it("test mysql read only detected by errno 1836", () => { - expect(mysqlHandler.isReadOnlyConnectionError(errorWith({ errno: 1836 }))).toBe(true); + it("test read only errno detected on an unwrapped error", () => { + expect(handler.isReadOnlyConnectionError(errorWith({ errno: 1290 }))).toBe(true); + }); }); - it("test mysql unrelated errno not detected", () => { - expect(mysqlHandler.isReadOnlyConnectionError(errorWith({ errno: 1064 }))).toBe(false); - }); + // pg hands the driver error to the plugins as-is. + describe("pg", () => { + const handler = new PgErrorHandler(); + + it("test read only sqlstate detected", () => { + expect(handler.isReadOnlyConnectionError(errorWith({ code: "25006" }))).toBe(true); + }); + + it("test unrelated sqlstate not detected", () => { + expect(handler.isReadOnlyConnectionError(errorWith({ code: "42601" }))).toBe(false); + }); + + it("test error without sqlstate not detected", () => { + expect(handler.isReadOnlyConnectionError(new Error("cannot execute INSERT in a read-only transaction"))).toBe(false); + }); - it("test mysql error without errno not detected", () => { - expect(mysqlHandler.isReadOnlyConnectionError(new Error("read only"))).toBe(false); + it("test read only sqlstate detected on a wrapped error", () => { + expect(handler.isReadOnlyConnectionError(asAwsWrapperError(errorWith({ code: "25006" })))).toBe(true); + }); }); });