diff --git a/packages/server/src/services/data-source-query/execute-query.test.ts b/packages/server/src/services/data-source-query/execute-query.test.ts index 3fe22569..b53e812a 100644 --- a/packages/server/src/services/data-source-query/execute-query.test.ts +++ b/packages/server/src/services/data-source-query/execute-query.test.ts @@ -587,6 +587,64 @@ describe("data source query execution", () => { }); }); + it("does not retry a PostgreSQL query error over plaintext", async () => { + const sqlError = Object.assign( + new Error("syntax error at or near SELECT"), + { + code: "42601", + } + ); + const { receivedConfigs, runner } = createPostgresRunner([ + { connectError: sqlError }, + { rows: [{ result: 1 }] }, + ]); + + const result = await executePostgresQuery( + { + ...postgresCredentials, + sslMode: "prefer", + type: "postgres", + }, + "SELECT broken", + undefined, + runner + ); + + expect(result.isErr()).toBe(true); + expect(receivedConfigs).toHaveLength(1); + expect(receivedConfigs[0]).toMatchObject({ + ssl: { rejectUnauthorized: true }, + }); + }); + + it("keeps plaintext fallback for a PostgreSQL connection exception", async () => { + const { receivedConfigs, runner } = createPostgresRunner([ + { + connectError: Object.assign(new Error("connection failure"), { + code: "08006", + }), + }, + { rows: [{ result: 1 }] }, + ]); + + const rows = unwrapQueryResult( + await executePostgresQuery( + { + ...postgresCredentials, + sslMode: "prefer", + type: "postgres", + }, + "SELECT 1", + undefined, + runner + ) + ); + + expect(rows).toEqual([{ result: 1 }]); + expect(receivedConfigs).toHaveLength(2); + expect(receivedConfigs[1]).toMatchObject({ ssl: false }); + }); + it("preserves the prior transport error when plaintext fallback also fails", async () => { const initialError = new Error("connection reset by peer"); const { receivedConfigs, runner } = createPostgresRunner([ diff --git a/packages/server/src/services/data-source-query/postgres-transport.ts b/packages/server/src/services/data-source-query/postgres-transport.ts index e08381c1..547f1381 100644 --- a/packages/server/src/services/data-source-query/postgres-transport.ts +++ b/packages/server/src/services/data-source-query/postgres-transport.ts @@ -69,6 +69,15 @@ export function resolvePostgresFailureTransitions( return []; } + // Non-connection SQLSTATE codes mean the server accepted the connection. + // Retrying the rejected query on plaintext cannot fix the SQL error and + // needlessly sends credentials and the query again over an insecure link. + // Class 08 is a connection exception, so keep its transport fallback. + const code = readErrorCode(error); + if (code && /^[0-9A-Z]{5}$/u.test(code) && !code.startsWith("08")) { + return []; + } + if (!isTlsVerificationError(error)) { return [ {