Summary
src/extract.rs's extract_value has no explicit case for Type::MONEY. It falls through to the generic fallback branch (_ => match row.try_get::<_, String>(index) { ... }), but String's FromSql::accepts returns false for Type::MONEY (confirmed directly: <String as FromSql>::accepts(&Type::MONEY) == false), so that fallback always fails and every MONEY column value — null or not — is returned as JSON null.
This is a real, user-visible correctness bug: MONEY is listed as a supported type in the README ("Supported PostgreSQL Data Types" table) and src/handlers/ddl.rs's numeric type group (line 277), but reading a MONEY column back always shows null in the data grid.
Builtin's approach (for reference)
The builtin driver (src-tauri/src/drivers/postgres/extract/advanced_types.rs) defines a dedicated wrapper:
/// Represents the total value in "cents" or the fractional unit of the database's locale.
pub struct Money(i64);
impl<'a> FromSql<'a> for Money {
fn from_sql(_ty: &Type, raw: &[u8]) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
Ok(Self(<i64 as FromSql>::from_sql(&Type::INT8, raw)?))
}
fn accepts(ty: &Type) -> bool {
matches!(*ty, Type::MONEY)
}
}
impl From<Money> for JsonValue {
fn from(value: Money) -> Self {
i64_to_json(value.0) // same JS-safe-integer stringification used for INT8
}
}
And dispatches it in extract/simple.rs:
Type::MONEY => JsonValue::from(from_sql_or_none::<advanced_types::Money>(ty, buf)),
MONEY is wire-encoded identically to INT8 (8-byte big-endian integer, in the smallest fractional unit / "cents") — the builtin just reinterprets the same bytes i64::from_sql would, then reuses the existing i64_to_json JS-safe-integer stringification (same helper this plugin's extract.rs already has for INT8).
Proposed fix
Add a Money newtype + FromSql impl to src/extract.rs (or a small local module) mirroring the builtin's, and an explicit Type::MONEY arm in extract_value's match, reusing the existing i64_to_json helper already in this file. Add a unit test asserting a MONEY column's wire bytes decode to the same JSON shape INT8 would for the same numeric value (plus one exceeding JS_MAX_SAFE_INTEGER to confirm the stringification kicks in, mirroring the builtin's test_money_above_js_safe_becomes_string).
Scope note: write path
The write path (src/binding.rs) also has no MONEY-specific handling on either side (checked: the builtin's binding.rs doesn't special-case MONEY either — inserting/updating a MONEY column falls to a plain TEXT bind on both drivers). That's parity-consistent, not a gap — no action needed there.
Discovery context
Found while auditing this plugin for other builtin-driver parity gaps following #34/#36/#38.
Summary
src/extract.rs'sextract_valuehas no explicit case forType::MONEY. It falls through to the generic fallback branch (_ => match row.try_get::<_, String>(index) { ... }), butString'sFromSql::acceptsreturnsfalseforType::MONEY(confirmed directly:<String as FromSql>::accepts(&Type::MONEY) == false), so that fallback always fails and every MONEY column value — null or not — is returned as JSONnull.This is a real, user-visible correctness bug:
MONEYis listed as a supported type in the README ("Supported PostgreSQL Data Types" table) andsrc/handlers/ddl.rs's numeric type group (line 277), but reading a MONEY column back always showsnullin the data grid.Builtin's approach (for reference)
The builtin driver (
src-tauri/src/drivers/postgres/extract/advanced_types.rs) defines a dedicated wrapper:And dispatches it in
extract/simple.rs:MONEYis wire-encoded identically toINT8(8-byte big-endian integer, in the smallest fractional unit / "cents") — the builtin just reinterprets the same bytesi64::from_sqlwould, then reuses the existingi64_to_jsonJS-safe-integer stringification (same helper this plugin'sextract.rsalready has forINT8).Proposed fix
Add a
Moneynewtype +FromSqlimpl tosrc/extract.rs(or a small local module) mirroring the builtin's, and an explicitType::MONEYarm inextract_value's match, reusing the existingi64_to_jsonhelper already in this file. Add a unit test asserting a MONEY column's wire bytes decode to the same JSON shapeINT8would for the same numeric value (plus one exceedingJS_MAX_SAFE_INTEGERto confirm the stringification kicks in, mirroring the builtin'stest_money_above_js_safe_becomes_string).Scope note: write path
The write path (
src/binding.rs) also has no MONEY-specific handling on either side (checked: the builtin'sbinding.rsdoesn't special-case MONEY either — inserting/updating a MONEY column falls to a plain TEXT bind on both drivers). That's parity-consistent, not a gap — no action needed there.Discovery context
Found while auditing this plugin for other builtin-driver parity gaps following #34/#36/#38.