fix(postgres): parse GaussDB boolean bytes

This commit is contained in:
gggaiitx 2026-07-28 22:39:10 +08:00 committed by GitHub
parent 06ed8dc5f6
commit 5f2fd528b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 60 additions and 2 deletions

View File

@ -523,7 +523,7 @@ pub(crate) fn pg_value_to_json_classified(row: &Row, idx: usize, col_type: PgCol
}
serde_json::Value::Null
}
PgColType::Bool => row.try_get::<_, bool>(idx).map(serde_json::Value::Bool).unwrap_or(serde_json::Value::Null),
PgColType::Bool => pg_bool_value_to_json(row, idx),
PgColType::Interval => row
.try_get::<_, PgInterval>(idx)
.map(|interval| serde_json::Value::String(format_pg_interval(interval)))
@ -2636,11 +2636,42 @@ fn parse_enum_values_from_row(row: &Row, index: usize) -> Option<Vec<String>> {
serde_json::from_str::<Vec<String>>(&raw).ok()
}
/// Decode a boolean column to JSON, tolerating databases (e.g. GaussDB) that
/// encode booleans as the ASCII bytes `t` (0x74) / `f` (0x66) in the binary
/// protocol instead of the standard PostgreSQL 0x00 / 0x01.
fn pg_bool_value_to_json(row: &Row, idx: usize) -> serde_json::Value {
if let Some(v) = pg_row_try_bool(row, idx) {
return serde_json::Value::Bool(v);
}
serde_json::Value::Null
}
/// Map raw boolean bytes to a Rust `bool`.
///
/// Standard PostgreSQL binary uses `[0x00]` / `[0x01]`; GaussDB sends the ASCII
/// text representation `[b't']` / `[b'f']` instead.
fn decode_bool_bytes(raw: &[u8]) -> Option<bool> {
match raw {
[0x00] => Some(false),
[0x01] => Some(true),
[b't'] | [b'T'] => Some(true),
[b'f'] | [b'F'] => Some(false),
_ => None,
}
}
fn decode_bool_candidates(raw: Option<&[u8]>, standard: Option<bool>) -> Option<bool> {
raw.and_then(decode_bool_bytes).or(standard)
}
/// Read a boolean column from a PostgreSQL row, tolerating databases that
/// encode booleans as integers (0/1) or text ('t'/'f') instead of the standard
/// `bool` OID. Returns `None` when the column is NULL or truly unreadable.
fn pg_row_try_bool(row: &Row, idx: usize) -> Option<bool> {
if let Ok(v) = row.try_get::<_, bool>(idx) {
// GaussDB encodes boolean as ASCII 't' (0x74) / 'f' (0x66) in binary.
let raw = row.try_get::<_, PgRawBytes>(idx).ok();
let standard = row.try_get::<_, bool>(idx).ok();
if let Some(v) = decode_bool_candidates(raw.as_ref().map(|value| value.0.as_slice()), standard) {
return Some(v);
}
if let Ok(v) = row.try_get::<_, i32>(idx) {
@ -4501,6 +4532,33 @@ mod tests {
assert_eq!(raw.0, vec![0x01, 0xAB, 0xFF]);
}
#[test]
fn decode_bool_bytes_handles_standard_and_gaussdb_encodings() {
// Standard PostgreSQL binary boolean: 0x00 / 0x01
assert_eq!(decode_bool_bytes(&[0x00]), Some(false));
assert_eq!(decode_bool_bytes(&[0x01]), Some(true));
// GaussDB binary boolean: ASCII 't' (0x74) / 'f' (0x66)
assert_eq!(decode_bool_bytes(&[0x74]), Some(true));
assert_eq!(decode_bool_bytes(&[0x66]), Some(false));
assert_eq!(decode_bool_bytes(b"t"), Some(true));
assert_eq!(decode_bool_bytes(b"f"), Some(false));
assert_eq!(decode_bool_bytes(b"T"), Some(true));
assert_eq!(decode_bool_bytes(b"F"), Some(false));
// Unrecognized encodings return None
assert_eq!(decode_bool_bytes(&[0x02]), None);
assert_eq!(decode_bool_bytes(&[0x74, 0x66]), None);
assert_eq!(decode_bool_bytes(&[]), None);
}
#[test]
fn raw_gaussdb_boolean_takes_precedence_over_standard_decoder() {
assert_eq!(decode_bool_candidates(Some(b"f"), Some(true)), Some(false));
assert_eq!(decode_bool_candidates(Some(b"t"), Some(true)), Some(true));
assert_eq!(decode_bool_candidates(Some(&[0x00]), Some(true)), Some(false));
assert_eq!(decode_bool_candidates(Some(&[0x01]), Some(false)), Some(true));
assert_eq!(decode_bool_candidates(Some(&[0x02]), Some(false)), Some(false));
}
#[test]
fn postgres_foreign_keys_sql_selects_referential_actions() {
let sql = postgres_foreign_keys_sql();