Files
bun.sh/src/sql/postgres/AnyPostgresError.zig
Ciro Spaciari 1e4935cf3e fix(Bun.SQL) fix postgres error handling when pipelining and state reset (#22505)
### What does this PR do?
Fixes: https://github.com/oven-sh/bun/issues/22395

### How did you verify your code works?
Test
2025-09-08 21:00:39 -07:00

134 lines
5.6 KiB
Zig

pub const AnyPostgresError = error{
ConnectionClosed,
ExpectedRequest,
ExpectedStatement,
InvalidBackendKeyData,
InvalidBinaryData,
InvalidByteSequence,
InvalidByteSequenceForEncoding,
InvalidCharacter,
InvalidMessage,
InvalidMessageLength,
InvalidQueryBinding,
InvalidServerKey,
InvalidServerSignature,
InvalidTimeFormat,
JSError,
MultidimensionalArrayNotSupportedYet,
NullsInArrayNotSupportedYet,
OutOfMemory,
Overflow,
PBKDFD2,
SASL_SIGNATURE_MISMATCH,
SASL_SIGNATURE_INVALID_BASE64,
ShortRead,
TLSNotAvailable,
TLSUpgradeFailed,
UnexpectedMessage,
UNKNOWN_AUTHENTICATION_METHOD,
UNSUPPORTED_AUTHENTICATION_METHOD,
UnsupportedByteaFormat,
UnsupportedIntegerSize,
UnsupportedArrayFormat,
UnsupportedNumericFormat,
UnknownFormatCode,
};
/// Options for creating a PostgresError
pub const PostgresErrorOptions = struct {
code: []const u8,
errno: ?[]const u8 = null,
detail: ?[]const u8 = null,
hint: ?[]const u8 = null,
severity: ?[]const u8 = null,
position: ?[]const u8 = null,
internalPosition: ?[]const u8 = null,
internalQuery: ?[]const u8 = null,
where: ?[]const u8 = null,
schema: ?[]const u8 = null,
table: ?[]const u8 = null,
column: ?[]const u8 = null,
dataType: ?[]const u8 = null,
constraint: ?[]const u8 = null,
file: ?[]const u8 = null,
line: ?[]const u8 = null,
routine: ?[]const u8 = null,
};
pub fn createPostgresError(
globalObject: *jsc.JSGlobalObject,
message: []const u8,
options: PostgresErrorOptions,
) bun.JSError!JSValue {
const opts_obj = JSValue.createEmptyObject(globalObject, 0);
opts_obj.ensureStillAlive();
opts_obj.put(globalObject, jsc.ZigString.static("code"), try bun.String.createUTF8ForJS(globalObject, options.code));
inline for (std.meta.fields(PostgresErrorOptions)) |field| {
const FieldType = @typeInfo(@TypeOf(@field(options, field.name)));
if (FieldType == .optional) {
if (@field(options, field.name)) |value| {
opts_obj.put(globalObject, jsc.ZigString.static(field.name), try bun.String.createUTF8ForJS(globalObject, value));
}
}
}
opts_obj.put(globalObject, jsc.ZigString.static("message"), try bun.String.createUTF8ForJS(globalObject, message));
return opts_obj;
}
pub fn postgresErrorToJS(globalObject: *jsc.JSGlobalObject, message: ?[]const u8, err: AnyPostgresError) JSValue {
const code = switch (err) {
error.ConnectionClosed => "ERR_POSTGRES_CONNECTION_CLOSED",
error.ExpectedRequest => "ERR_POSTGRES_EXPECTED_REQUEST",
error.ExpectedStatement => "ERR_POSTGRES_EXPECTED_STATEMENT",
error.InvalidBackendKeyData => "ERR_POSTGRES_INVALID_BACKEND_KEY_DATA",
error.InvalidBinaryData => "ERR_POSTGRES_INVALID_BINARY_DATA",
error.InvalidByteSequence => "ERR_POSTGRES_INVALID_BYTE_SEQUENCE",
error.InvalidByteSequenceForEncoding => "ERR_POSTGRES_INVALID_BYTE_SEQUENCE_FOR_ENCODING",
error.InvalidCharacter => "ERR_POSTGRES_INVALID_CHARACTER",
error.InvalidMessage => "ERR_POSTGRES_INVALID_MESSAGE",
error.InvalidMessageLength => "ERR_POSTGRES_INVALID_MESSAGE_LENGTH",
error.InvalidQueryBinding => "ERR_POSTGRES_INVALID_QUERY_BINDING",
error.InvalidServerKey => "ERR_POSTGRES_INVALID_SERVER_KEY",
error.InvalidServerSignature => "ERR_POSTGRES_INVALID_SERVER_SIGNATURE",
error.InvalidTimeFormat => "ERR_POSTGRES_INVALID_TIME_FORMAT",
error.MultidimensionalArrayNotSupportedYet => "ERR_POSTGRES_MULTIDIMENSIONAL_ARRAY_NOT_SUPPORTED_YET",
error.NullsInArrayNotSupportedYet => "ERR_POSTGRES_NULLS_IN_ARRAY_NOT_SUPPORTED_YET",
error.Overflow => "ERR_POSTGRES_OVERFLOW",
error.PBKDFD2 => "ERR_POSTGRES_AUTHENTICATION_FAILED_PBKDF2",
error.SASL_SIGNATURE_MISMATCH => "ERR_POSTGRES_SASL_SIGNATURE_MISMATCH",
error.SASL_SIGNATURE_INVALID_BASE64 => "ERR_POSTGRES_SASL_SIGNATURE_INVALID_BASE64",
error.TLSNotAvailable => "ERR_POSTGRES_TLS_NOT_AVAILABLE",
error.TLSUpgradeFailed => "ERR_POSTGRES_TLS_UPGRADE_FAILED",
error.UnexpectedMessage => "ERR_POSTGRES_UNEXPECTED_MESSAGE",
error.UNKNOWN_AUTHENTICATION_METHOD => "ERR_POSTGRES_UNKNOWN_AUTHENTICATION_METHOD",
error.UNSUPPORTED_AUTHENTICATION_METHOD => "ERR_POSTGRES_UNSUPPORTED_AUTHENTICATION_METHOD",
error.UnsupportedByteaFormat => "ERR_POSTGRES_UNSUPPORTED_BYTEA_FORMAT",
error.UnsupportedArrayFormat => "ERR_POSTGRES_UNSUPPORTED_ARRAY_FORMAT",
error.UnsupportedIntegerSize => "ERR_POSTGRES_UNSUPPORTED_INTEGER_SIZE",
error.UnsupportedNumericFormat => "ERR_POSTGRES_UNSUPPORTED_NUMERIC_FORMAT",
error.UnknownFormatCode => "ERR_POSTGRES_UNKNOWN_FORMAT_CODE",
error.JSError => {
return globalObject.takeException(error.JSError);
},
error.OutOfMemory => {
// TODO: add binding for creating an out of memory error?
return globalObject.takeException(globalObject.throwOutOfMemory());
},
error.ShortRead => {
bun.unreachablePanic("Assertion failed: ShortRead should be handled by the caller in postgres", .{});
},
};
var buffer_message = [_]u8{0} ** 256;
const msg = message orelse std.fmt.bufPrint(buffer_message[0..], "Failed to bind query: {s}", .{@errorName(err)}) catch "Failed to bind query";
return createPostgresError(globalObject, msg, .{ .code = code }) catch |e| globalObject.takeError(e);
}
const bun = @import("bun");
const std = @import("std");
const jsc = bun.jsc;
const JSValue = jsc.JSValue;